Commit e6f2fad3 authored by Himanshu Kapoor's avatar Himanshu Kapoor

Remove `binary` from the file object

Use utility function isTextFile to determine whether its a text file
or binary
parent 6dcafaf5
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { GlLink, GlTooltipDirective } from '@gitlab/ui'; import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import TerminalSyncStatusSafe from './terminal_sync/terminal_sync_status_safe.vue'; import TerminalSyncStatusSafe from './terminal_sync/terminal_sync_status_safe.vue';
import { getFileEOL } from '../utils'; import { isTextFile, getFileEOL } from '~/ide/utils';
export default { export default {
components: { components: {
...@@ -17,6 +17,9 @@ export default { ...@@ -17,6 +17,9 @@ export default {
activeFileEOL() { activeFileEOL() {
return getFileEOL(this.activeFile.content); return getFileEOL(this.activeFile.content);
}, },
activeFileIsText() {
return isTextFile(this.activeFile.name, this.activeFile.content);
},
}, },
}; };
</script> </script>
...@@ -30,7 +33,7 @@ export default { ...@@ -30,7 +33,7 @@ export default {
</gl-link> </gl-link>
</div> </div>
<div>{{ activeFileEOL }}</div> <div>{{ activeFileEOL }}</div>
<div v-if="!activeFile.binary">{{ activeFile.editorRow }}:{{ activeFile.editorColumn }}</div> <div v-if="activeFileIsText">{{ activeFile.editorRow }}:{{ activeFile.editorColumn }}</div>
<div>{{ activeFile.fileLanguage }}</div> <div>{{ activeFile.fileLanguage }}</div>
</template> </template>
<terminal-sync-status-safe /> <terminal-sync-status-safe />
......
...@@ -28,14 +28,13 @@ export default { ...@@ -28,14 +28,13 @@ export default {
const { name } = file; const { name } = file;
const encodedContent = target.result.split('base64,')[1]; const encodedContent = target.result.split('base64,')[1];
const rawContent = encodedContent ? atob(encodedContent) : ''; const rawContent = encodedContent ? atob(encodedContent) : '';
const isText = isTextFile(rawContent, file.type, name); const isText = isTextFile(name, rawContent, file.type);
const emitCreateEvent = content => const emitCreateEvent = content =>
this.$emit('create', { this.$emit('create', {
name: `${this.path ? `${this.path}/` : ''}${name}`, name: `${this.path ? `${this.path}/` : ''}${name}`,
type: 'blob', type: 'blob',
content, content,
binary: !isText,
rawPath: !isText ? target.result : '', rawPath: !isText ? target.result : '',
}); });
......
...@@ -14,7 +14,7 @@ import Editor from '../lib/editor'; ...@@ -14,7 +14,7 @@ import Editor from '../lib/editor';
import FileTemplatesBar from './file_templates/bar.vue'; import FileTemplatesBar from './file_templates/bar.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { extractMarkdownImagesFromEntries } from '../stores/utils'; import { extractMarkdownImagesFromEntries } from '../stores/utils';
import { getPathParent, readFileAsDataURL, registerSchema } from '../utils'; import { getPathParent, readFileAsDataURL, registerSchema, isTextFile } from '../utils';
import { getRulesWithTraversal } from '../lib/editorconfig/parser'; import { getRulesWithTraversal } from '../lib/editorconfig/parser';
import mapRulesToMonaco from '../lib/editorconfig/rules_mapper'; import mapRulesToMonaco from '../lib/editorconfig/rules_mapper';
...@@ -60,7 +60,7 @@ export default { ...@@ -60,7 +60,7 @@ export default {
]), ]),
...mapGetters('fileTemplates', ['showFileTemplatesBar']), ...mapGetters('fileTemplates', ['showFileTemplatesBar']),
shouldHideEditor() { shouldHideEditor() {
return this.file && this.file.binary; return this.file && !isTextFile(this.file.name, this.file.content);
}, },
showContentViewer() { showContentViewer() {
return ( return (
......
import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
import { decorateData, sortTree } from '../stores/utils'; import { decorateData, sortTree } from '../stores/utils';
export const splitParent = path => { export const splitParent = path => {
...@@ -13,13 +12,7 @@ export const splitParent = path => { ...@@ -13,13 +12,7 @@ export const splitParent = path => {
/** /**
* Create file objects from a list of file paths. * Create file objects from a list of file paths.
*/ */
export const decorateFiles = ({ export const decorateFiles = ({ data, tempFile = false, content = '', rawPath = '' }) => {
data,
tempFile = false,
content = '',
binary = false,
rawPath = '',
}) => {
const treeList = []; const treeList = [];
const entries = {}; const entries = {};
...@@ -68,7 +61,6 @@ export const decorateFiles = ({ ...@@ -68,7 +61,6 @@ export const decorateFiles = ({
const fileFolder = parent && insertParent(parent); const fileFolder = parent && insertParent(parent);
if (name) { if (name) {
const previewMode = viewerInformationForPath(name);
parentPath = fileFolder && fileFolder.path; parentPath = fileFolder && fileFolder.path;
file = decorateData({ file = decorateData({
...@@ -79,7 +71,6 @@ export const decorateFiles = ({ ...@@ -79,7 +71,6 @@ export const decorateFiles = ({
tempFile, tempFile,
changed: tempFile, changed: tempFile,
content, content,
binary: (previewMode && previewMode.binary) || binary,
rawPath, rawPath,
parentPath, parentPath,
}); });
......
...@@ -25,15 +25,7 @@ export const setResizingStatus = ({ commit }, resizing) => { ...@@ -25,15 +25,7 @@ export const setResizingStatus = ({ commit }, resizing) => {
export const createTempEntry = ( export const createTempEntry = (
{ state, commit, dispatch, getters }, { state, commit, dispatch, getters },
{ { name, type, content = '', rawPath = '', openFile = true, makeFileActive = true },
name,
type,
content = '',
binary = false,
rawPath = '',
openFile = true,
makeFileActive = true,
},
) => { ) => {
const fullName = name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name; const fullName = name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name;
...@@ -57,7 +49,6 @@ export const createTempEntry = ( ...@@ -57,7 +49,6 @@ export const createTempEntry = (
type, type,
tempFile: true, tempFile: true,
content, content,
binary,
rawPath, rawPath,
}); });
const { file, parentPath } = data; const { file, parentPath } = data;
...@@ -84,7 +75,6 @@ export const addTempImage = ({ dispatch, getters }, { name, rawPath = '' }) => ...@@ -84,7 +75,6 @@ export const addTempImage = ({ dispatch, getters }, { name, rawPath = '' }) =>
name: getters.getAvailableFileName(name), name: getters.getAvailableFileName(name),
type: 'blob', type: 'blob',
content: rawPath.split('base64,')[1], content: rawPath.split('base64,')[1],
binary: true,
rawPath, rawPath,
openFile: false, openFile: false,
makeFileActive: false, makeFileActive: false,
......
...@@ -23,7 +23,6 @@ export const dataStructure = () => ({ ...@@ -23,7 +23,6 @@ export const dataStructure = () => ({
staged: false, staged: false,
lastCommitSha: '', lastCommitSha: '',
rawPath: '', rawPath: '',
binary: false,
raw: '', raw: '',
content: '', content: '',
editorRow: 1, editorRow: 1,
...@@ -49,7 +48,6 @@ export const decorateData = entity => { ...@@ -49,7 +48,6 @@ export const decorateData = entity => {
active = false, active = false,
opened = false, opened = false,
changed = false, changed = false,
binary = false,
rawPath = '', rawPath = '',
file_lock, file_lock,
parentPath = '', parentPath = '',
...@@ -66,7 +64,6 @@ export const decorateData = entity => { ...@@ -66,7 +64,6 @@ export const decorateData = entity => {
active, active,
changed, changed,
content, content,
binary,
rawPath, rawPath,
file_lock, file_lock,
parentPath, parentPath,
......
import { languages } from 'monaco-editor'; import { languages } from 'monaco-editor';
import { flatten } from 'lodash'; import { flatten, isString } from 'lodash';
import { SIDE_LEFT, SIDE_RIGHT } from './constants'; import { SIDE_LEFT, SIDE_RIGHT } from './constants';
const toLowerCase = x => x.toLowerCase(); const toLowerCase = x => x.toLowerCase();
...@@ -42,15 +42,16 @@ const KNOWN_TYPES = [ ...@@ -42,15 +42,16 @@ const KNOWN_TYPES = [
}, },
]; ];
export function isTextFile(content, mimeType, fileName) { export function isTextFile(fileName, content, mimeType = '') {
const knownType = KNOWN_TYPES.find(type => type.isMatch(mimeType, fileName)); const knownType = KNOWN_TYPES.find(type => type.isMatch(mimeType, fileName));
if (knownType) return knownType.isText; if (knownType) return knownType.isText;
// does the string contain ascii characters only (ranges from space to tilde, tabs and new lines) // does the string contain ascii characters only (ranges from space to tilde, tabs and new lines)
const asciiRegex = /^[ -~\t\n\r]+$/; const asciiRegex = /^[ -~\t\n\r]+$/;
// for unknown types, determine the type by evaluating the file contents // for unknown types, determine the type by evaluating the file contents
return asciiRegex.test(content); return isString(content) && (content === '' || asciiRegex.test(content));
} }
export const createPathWithExt = p => { export const createPathWithExt = p => {
......
...@@ -75,7 +75,8 @@ describe('ide/components/ide_status_list', () => { ...@@ -75,7 +75,8 @@ describe('ide/components/ide_status_list', () => {
describe('with binary file', () => { describe('with binary file', () => {
beforeEach(() => { beforeEach(() => {
activeFile.binary = true; activeFile.name = 'abc.dat';
activeFile.content = '🐱'; // non-ascii binary content
createComponent(); createComponent();
}); });
......
...@@ -85,7 +85,6 @@ describe('new dropdown upload', () => { ...@@ -85,7 +85,6 @@ describe('new dropdown upload', () => {
name: textFile.name, name: textFile.name,
type: 'blob', type: 'blob',
content: 'plain text', content: 'plain text',
binary: false,
rawPath: '', rawPath: '',
}); });
}) })
...@@ -102,7 +101,6 @@ describe('new dropdown upload', () => { ...@@ -102,7 +101,6 @@ describe('new dropdown upload', () => {
name: binaryFile.name, name: binaryFile.name,
type: 'blob', type: 'blob',
content: binaryTarget.result.split('base64,')[1], content: binaryTarget.result.split('base64,')[1],
binary: true,
rawPath: binaryTarget.result, rawPath: binaryTarget.result,
}); });
}); });
......
...@@ -45,7 +45,7 @@ describe('RepoEditor', () => { ...@@ -45,7 +45,7 @@ describe('RepoEditor', () => {
const createOpenFile = path => { const createOpenFile = path => {
const origFile = store.state.openFiles[0]; const origFile = store.state.openFiles[0];
const newFile = { ...origFile, path, key: path }; const newFile = { ...origFile, path, key: path, name: 'myfile.txt', content: 'hello world' };
store.state.entries[path] = newFile; store.state.entries[path] = newFile;
...@@ -54,8 +54,9 @@ describe('RepoEditor', () => { ...@@ -54,8 +54,9 @@ describe('RepoEditor', () => {
beforeEach(() => { beforeEach(() => {
const f = { const f = {
...file(), ...file('file.txt'),
viewMode: FILE_VIEW_MODE_EDITOR, viewMode: FILE_VIEW_MODE_EDITOR,
content: 'hello world',
}; };
const storeOptions = createStoreOptions(); const storeOptions = createStoreOptions();
...@@ -142,6 +143,7 @@ describe('RepoEditor', () => { ...@@ -142,6 +143,7 @@ describe('RepoEditor', () => {
...vm.file, ...vm.file,
projectId: 'namespace/project', projectId: 'namespace/project',
path: 'sample.md', path: 'sample.md',
name: 'sample.md',
content: 'testing 123', content: 'testing 123',
}); });
...@@ -200,7 +202,8 @@ describe('RepoEditor', () => { ...@@ -200,7 +202,8 @@ describe('RepoEditor', () => {
describe('when open file is binary and not raw', () => { describe('when open file is binary and not raw', () => {
beforeEach(done => { beforeEach(done => {
vm.file.binary = true; vm.file.name = 'file.dat';
vm.file.content = '🐱'; // non-ascii binary content
vm.$nextTick(done); vm.$nextTick(done);
}); });
...@@ -407,6 +410,9 @@ describe('RepoEditor', () => { ...@@ -407,6 +410,9 @@ describe('RepoEditor', () => {
beforeEach(done => { beforeEach(done => {
jest.spyOn(vm.editor, 'updateDimensions').mockImplementation(); jest.spyOn(vm.editor, 'updateDimensions').mockImplementation();
vm.file.viewMode = FILE_VIEW_MODE_PREVIEW; vm.file.viewMode = FILE_VIEW_MODE_PREVIEW;
vm.file.name = 'myfile.md';
vm.file.content = 'hello world';
vm.$nextTick(done); vm.$nextTick(done);
}); });
...@@ -650,7 +656,6 @@ describe('RepoEditor', () => { ...@@ -650,7 +656,6 @@ describe('RepoEditor', () => {
path: 'foo/foo.png', path: 'foo/foo.png',
type: 'blob', type: 'blob',
content: 'Zm9v', content: 'Zm9v',
binary: true,
rawPath: 'data:image/png;base64,Zm9v', rawPath: 'data:image/png;base64,Zm9v',
}); });
}); });
......
import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
import { decorateFiles, splitParent } from '~/ide/lib/files'; import { decorateFiles, splitParent } from '~/ide/lib/files';
import { decorateData } from '~/ide/stores/utils'; import { decorateData } from '~/ide/stores/utils';
const createEntries = paths => { const createEntries = paths => {
const createEntry = (acc, { path, type, children }) => { const createEntry = (acc, { path, type, children }) => {
const { name, parent } = splitParent(path); const { name, parent } = splitParent(path);
const previewMode = viewerInformationForPath(name);
acc[path] = { acc[path] = {
...decorateData({ ...decorateData({
...@@ -13,8 +11,6 @@ const createEntries = paths => { ...@@ -13,8 +11,6 @@ const createEntries = paths => {
name, name,
path, path,
type, type,
previewMode,
binary: (previewMode && previewMode.binary) || false,
parentPath: parent, parentPath: parent,
}), }),
tree: children.map(childName => expect.objectContaining({ name: childName })), tree: children.map(childName => expect.objectContaining({ name: childName })),
......
...@@ -241,7 +241,6 @@ describe('IDE store file actions', () => { ...@@ -241,7 +241,6 @@ describe('IDE store file actions', () => {
200, 200,
{ {
raw_path: 'raw_path', raw_path: 'raw_path',
binary: false,
}, },
{ {
'page-title': 'testing getFileData', 'page-title': 'testing getFileData',
...@@ -305,7 +304,6 @@ describe('IDE store file actions', () => { ...@@ -305,7 +304,6 @@ describe('IDE store file actions', () => {
200, 200,
{ {
raw_path: 'raw_path', raw_path: 'raw_path',
binary: false,
}, },
{ {
'page-title': 'testing old-dull-file', 'page-title': 'testing old-dull-file',
......
...@@ -61,13 +61,11 @@ describe('IDE store file mutations', () => { ...@@ -61,13 +61,11 @@ describe('IDE store file mutations', () => {
mutations.SET_FILE_DATA(localState, { mutations.SET_FILE_DATA(localState, {
data: { data: {
raw_path: 'raw', raw_path: 'raw',
binary: true,
}, },
file: localFile, file: localFile,
}); });
expect(localFile.rawPath).toBe('raw'); expect(localFile.rawPath).toBe('raw');
expect(localFile.binary).toBeTruthy();
expect(localFile.raw).toBeNull(); expect(localFile.raw).toBeNull();
expect(localFile.baseRaw).toBeNull(); expect(localFile.baseRaw).toBeNull();
}); });
......
...@@ -14,59 +14,77 @@ import { ...@@ -14,59 +14,77 @@ import {
describe('WebIDE utils', () => { describe('WebIDE utils', () => {
describe('isTextFile', () => { describe('isTextFile', () => {
it('returns false for known binary types', () => { it('returns false for known binary types', () => {
expect(isTextFile('file content', 'image/png', 'my.png')).toBeFalsy(); expect(isTextFile('my.png', 'file content', 'image/png')).toBeFalsy();
// mime types are case insensitive // mime types are case insensitive
expect(isTextFile('file content', 'IMAGE/PNG', 'my.png')).toBeFalsy(); expect(isTextFile('my.png', 'file content', 'IMAGE/PNG')).toBeFalsy();
}); });
it('returns true for known text types', () => { it('returns true for known text types', () => {
expect(isTextFile('file content', 'text/plain', 'my.txt')).toBeTruthy(); expect(isTextFile('my.txt', 'file content', 'text/plain')).toBeTruthy();
// mime types are case insensitive // mime types are case insensitive
expect(isTextFile('file content', 'TEXT/PLAIN', 'my.txt')).toBeTruthy(); expect(isTextFile('my.txt', 'file content', 'TEXT/PLAIN')).toBeTruthy();
}); });
it('returns true for file extensions that Monaco supports syntax highlighting for', () => { it('returns true for file extensions that Monaco supports syntax highlighting for', () => {
// test based on both MIME and extension // test based on both MIME and extension
expect(isTextFile('{"éêė":"value"}', 'application/json', 'my.json')).toBeTruthy(); expect(isTextFile('my.json', '{"éêė":"value"}', 'application/json')).toBeTruthy();
expect(isTextFile('{"éêė":"value"}', 'application/json', '.tsconfig')).toBeTruthy(); expect(isTextFile('.tsconfig', '{"éêė":"value"}', 'application/json')).toBeTruthy();
expect(isTextFile('SELECT "éêė" from tablename', 'application/sql', 'my.sql')).toBeTruthy(); expect(isTextFile('my.sql', 'SELECT "éêė" from tablename', 'application/sql')).toBeTruthy();
}); });
it('returns true even irrespective of whether the mimes, extensions or file names are lowercase or upper case', () => { it('returns true even irrespective of whether the mimes, extensions or file names are lowercase or upper case', () => {
expect(isTextFile('{"éêė":"value"}', 'application/json', 'MY.JSON')).toBeTruthy(); expect(isTextFile('MY.JSON', '{"éêė":"value"}', 'application/json')).toBeTruthy();
expect(isTextFile('SELECT "éêė" from tablename', 'application/sql', 'MY.SQL')).toBeTruthy(); expect(isTextFile('MY.SQL', 'SELECT "éêė" from tablename', 'application/sql')).toBeTruthy();
expect( expect(
isTextFile('var code = "something"', 'application/javascript', 'Gruntfile'), isTextFile('Gruntfile', 'var code = "something"', 'application/javascript'),
).toBeTruthy(); ).toBeTruthy();
expect( expect(
isTextFile( isTextFile(
'dockerfile',
'MAINTAINER Александр "alexander11354322283@me.com"', 'MAINTAINER Александр "alexander11354322283@me.com"',
'application/octet-stream', 'application/octet-stream',
'dockerfile',
), ),
).toBeTruthy(); ).toBeTruthy();
}); });
it('returns false if filename is same as the expected extension', () => { it('returns false if filename is same as the expected extension', () => {
expect(isTextFile('SELECT "éêė" from tablename', 'application/sql', 'sql')).toBeFalsy(); expect(isTextFile('sql', 'SELECT "éêė" from tablename', 'application/sql')).toBeFalsy();
}); });
it('returns true for ASCII only content for unknown types', () => { it('returns true for ASCII only content for unknown types', () => {
expect(isTextFile('plain text', 'application/x-new-type', 'hello.mytype')).toBeTruthy(); expect(isTextFile('hello.mytype', 'plain text', 'application/x-new-type')).toBeTruthy();
}); });
it('returns true for relevant filenames', () => { it('returns true for relevant filenames', () => {
expect( expect(
isTextFile( isTextFile(
'Dockerfile',
'MAINTAINER Александр "alexander11354322283@me.com"', 'MAINTAINER Александр "alexander11354322283@me.com"',
'application/octet-stream', 'application/octet-stream',
'Dockerfile',
), ),
).toBeTruthy(); ).toBeTruthy();
}); });
it('returns false for non-ASCII content for unknown types', () => { it('returns false for non-ASCII content for unknown types', () => {
expect(isTextFile('{"éêė":"value"}', 'application/octet-stream', 'my.random')).toBeFalsy(); expect(isTextFile('my.random', '{"éêė":"value"}', 'application/octet-stream')).toBeFalsy();
});
it.each`
filename | result
${'myfile.txt'} | ${true}
${'Dockerfile'} | ${true}
${'img.png'} | ${false}
${'abc.js'} | ${true}
${'abc.random'} | ${false}
${'image.jpeg'} | ${false}
`('returns $result for $filename', ({ filename, result }) => {
expect(isTextFile(filename)).toBe(result);
});
it('returns true if content is empty string but false if content is not passed', () => {
expect(isTextFile('abc.dat')).toBe(false);
expect(isTextFile('abc.dat', '')).toBe(true);
expect(isTextFile('abc.dat', ' ')).toBe(true);
}); });
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment