diff --git a/src/core/root.ts b/src/core/root.ts index 74bccbaab..bcc3d25f8 100644 --- a/src/core/root.ts +++ b/src/core/root.ts @@ -27,8 +27,7 @@ export const root = { findFromMagic, findFromActive, findFromRoot, - findInWorkspace, - findSubfiles + findInWorkspace } } @@ -264,19 +263,16 @@ function findFromActive(): string | undefined { * if not found. */ function findSubfiles(content: string): string | undefined { - if (!vscode.window.activeTextEditor) { - return - } const regex = /(?:\\documentclass\[(.*)\]{subfiles})/s const result = content.match(regex) - if (result) { - const filePath = utils.resolveFile([path.dirname(vscode.window.activeTextEditor.document.fileName)], result[1]) - if (filePath) { - logger.log(`Found subfile root ${filePath} from active.`) - } - return filePath + if (!result) { + return } - return + const filePath = utils.resolveFile([path.dirname(vscode.window.activeTextEditor!.document.fileName)], result[1]) + if (filePath) { + logger.log(`Found subfile root ${filePath} from active.`) + } + return filePath } /** diff --git a/test/fixtures/unittest/04_core_root/find_active/main.tex b/test/fixtures/unittest/04_core_root/find_active/main.tex new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/unittest/04_core_root/find_root/root.tex b/test/fixtures/unittest/04_core_root/find_root/root.tex new file mode 100644 index 000000000..bb09c351e --- /dev/null +++ b/test/fixtures/unittest/04_core_root/find_root/root.tex @@ -0,0 +1 @@ +\input{../main} \ No newline at end of file diff --git a/test/fixtures/unittest/04_core_root/find_root/root_no_input.tex b/test/fixtures/unittest/04_core_root/find_root/root_no_input.tex new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/unittest/04_core_root/find_workspace/another.tex b/test/fixtures/unittest/04_core_root/find_workspace/another.tex new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/unittest/04_core_root/find_workspace/comment.tex b/test/fixtures/unittest/04_core_root/find_workspace/comment.tex new file mode 100644 index 000000000..f2db44277 --- /dev/null +++ b/test/fixtures/unittest/04_core_root/find_workspace/comment.tex @@ -0,0 +1 @@ +% \documentclass{article} diff --git a/test/fixtures/unittest/04_core_root/find_workspace/main.fls b/test/fixtures/unittest/04_core_root/find_workspace/main.fls new file mode 100644 index 000000000..e8fa27807 --- /dev/null +++ b/test/fixtures/unittest/04_core_root/find_workspace/main.fls @@ -0,0 +1 @@ +INPUT another.tex \ No newline at end of file diff --git a/test/fixtures/unittest/04_core_root/find_workspace/main.tex b/test/fixtures/unittest/04_core_root/find_workspace/main.tex new file mode 100644 index 000000000..afc4251d1 --- /dev/null +++ b/test/fixtures/unittest/04_core_root/find_workspace/main.tex @@ -0,0 +1 @@ +\documentclass{article} diff --git a/test/fixtures/unittest/04_core_root/find_workspace/parent.tex b/test/fixtures/unittest/04_core_root/find_workspace/parent.tex new file mode 100644 index 000000000..8230f2cdb --- /dev/null +++ b/test/fixtures/unittest/04_core_root/find_workspace/parent.tex @@ -0,0 +1,2 @@ +\documentclass{article} +\input{another.tex} diff --git a/test/units/03_core_cache.test.ts b/test/units/03_core_cache.test.ts index de5d9e12e..c1d0cfc6a 100644 --- a/test/units/03_core_cache.test.ts +++ b/test/units/03_core_cache.test.ts @@ -828,6 +828,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { it('should return a list of de-duplicated .tex files', async () => { const toParse = get.path(fixture, 'included_tex', 'duplicate_1.tex') await lw.cache.refreshCache(toParse) + await lw.cache.wait(get.path(fixture, 'included_tex', 'another.tex')) assert.listStrictEqual(lw.cache.getIncludedTeX(toParse), [ get.path(fixture, 'included_tex', 'duplicate_1.tex'), get.path(fixture, 'included_tex', 'duplicate_2.tex'), diff --git a/test/units/04_core_root.test.ts b/test/units/04_core_root.test.ts index 6b6341274..4886ea875 100644 --- a/test/units/04_core_root.test.ts +++ b/test/units/04_core_root.test.ts @@ -229,4 +229,200 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.strictEqual(root, rootPath) }) }) + + describe('lw.root.findFromRoot', () => { + it('should return undefined if there is no active editor', () => { + const stub = sinon.stub(vscode.window, 'activeTextEditor').value(undefined) + const root = lw.root._test.findFromRoot() + stub.restore() + + assert.strictEqual(root, undefined) + }) + + it('should return undefined if there is no root', () => { + const texPath = get.path(fixture, 'main.tex') + + const stub = mock.activeTextEditor(texPath, '') + const root = lw.root._test.findFromRoot() + stub.restore() + + assert.strictEqual(root, undefined) + }) + + it('should return undefined if active editor is not a file', () => { + const texPath = get.path(fixture, 'main.tex') + + set.root(texPath) + const stub = mock.activeTextEditor('https://google.com', '', { scheme: 'https' }) + const root = lw.root._test.findFromRoot() + stub.restore() + + assert.strictEqual(root, undefined) + }) + + it('should find root if active file is in the root tex tree', async () => { + const texPath = get.path(fixture, 'main.tex') + const toParse = get.path(fixture, 'find_root', 'root.tex') + + set.root(toParse) + await lw.cache.refreshCache(toParse) + const stub = mock.activeTextEditor(texPath, '') + const root = lw.root._test.findFromRoot() + stub.restore() + + assert.strictEqual(root, toParse) + }) + + it('should return undefined if active file is not in the root tex tree', async () => { + const texPath = get.path(fixture, 'main.tex') + const toParse = get.path(fixture, 'find_root', 'root_no_input.tex') + + set.root(toParse) + await lw.cache.refreshCache(toParse) + const stub = mock.activeTextEditor(texPath, '') + const root = lw.root._test.findFromRoot() + stub.restore() + + assert.strictEqual(root, undefined) + }) + }) + + describe('lw.root.findFromActive', () => { + beforeEach(async () => { + await set.config('latex.rootFile.indicator', '\\documentclass[]{}') + }) + + it('should return undefined if there is no active editor', () => { + const stub = sinon.stub(vscode.window, 'activeTextEditor').value(undefined) + const root = lw.root._test.findFromActive() + stub.restore() + + assert.strictEqual(root, undefined) + }) + + it('should return undefined if active editor is not a file', () => { + const texPath = get.path(fixture, 'main.tex') + + set.root(texPath) + const stub = mock.activeTextEditor('https://google.com', '', { scheme: 'https' }) + const root = lw.root._test.findFromActive() + stub.restore() + + assert.strictEqual(root, undefined) + }) + + it('should find root if active file has root file indicator', () => { + const texPath = get.path(fixture, 'main.tex') + + const stub = mock.activeTextEditor(texPath, '\\documentclass{article}\n') + const root = lw.root._test.findFromActive() + stub.restore() + + assert.strictEqual(root, texPath) + }) + + it('should ignore root file indicators in comments', () => { + const texPath = get.path(fixture, 'main.tex') + + const stub = mock.activeTextEditor(texPath, '% \\documentclass{article}\n') + const root = lw.root._test.findFromActive() + stub.restore() + + assert.strictEqual(root, undefined) + }) + + it('should find subfile root if active file is a subfile', () => { + const texPath = get.path(fixture, 'main.tex') + + const stub = mock.activeTextEditor(texPath, '\\documentclass[find_active/main.tex]{subfiles}\n') + const root = lw.root._test.findFromActive() + stub.restore() + + assert.strictEqual(root, get.path(fixture, 'find_active', 'main.tex')) + }) + + it('should find root if active file is a subfile but points to non-existing file', () => { + const texPath = get.path(fixture, 'main.tex') + + const stub = mock.activeTextEditor(texPath, '\\documentclass[find_active/nothing.tex]{subfiles}\n') + const root = lw.root._test.findFromActive() + stub.restore() + + assert.strictEqual(root, texPath) + }) + }) + + describe('lw.root.findInWorkspace', () => { + beforeEach(async () => { + await set.config('latex.rootFile.indicator', '\\documentclass[]{}') + }) + + it('should follow `latex.search.rootFiles.include` config', async () => { + await set.config('latex.search.rootFiles.include', [ 'absolutely-nothing.tex' ]) + const root = await lw.root._test.findInWorkspace() + + assert.strictEqual(root, undefined) + }) + + it('should follow `latex.search.rootFiles.exclude` config', async () => { + await set.config('latex.search.rootFiles.exclude', [ '**/*' ]) + const root = await lw.root._test.findInWorkspace() + + assert.strictEqual(root, undefined) + }) + + it('should find the correct root from workspace', async () => { + const texPath = get.path(fixture, 'find_workspace', 'main.tex') + + await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + await set.config('latex.search.rootFiles.exclude', [ `${fixture}/find_workspace/**/parent.tex` ]) + const root = await lw.root._test.findInWorkspace() + + assert.strictEqual(root, texPath) + }) + + it('should ignore root file indicators in comments', async () => { + await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/comment.tex` ]) + const root = await lw.root._test.findInWorkspace() + + assert.strictEqual(root, undefined) + }) + + it('should find the correct root if the .fls of root includes active editor', async () => { + const texPath = get.path(fixture, 'find_workspace', 'main.tex') + const texPathAnother = get.path(fixture, 'find_workspace', 'another.tex') + + await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + const stub = mock.activeTextEditor(texPathAnother, '\\documentclass{article}\n') + const root = await lw.root._test.findInWorkspace() + stub.restore() + + assert.strictEqual(root, texPath) + }) + + it('should find the correct root if the children of root includes active editor', async () => { + const texPath = get.path(fixture, 'find_workspace', 'parent.tex') + const texPathAnother = get.path(fixture, 'find_workspace', 'another.tex') + + await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + await lw.cache.refreshCache(texPath) + const stub = mock.activeTextEditor(texPathAnother, '\\documentclass{article}\n') + const root = await lw.root._test.findInWorkspace() + stub.restore() + + assert.strictEqual(root, texPath) + }) + + it('should find the correct root if current root is in the candidates', async () => { + const texPath = get.path(fixture, 'find_workspace', 'main.tex') + + await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + set.root(fixture, 'find_workspace', 'main.tex') + const stub = mock.activeTextEditor(texPath, '\\documentclass{article}\n') + const root = await lw.root._test.findInWorkspace() + stub.restore() + + assert.strictEqual(root, texPath) + }) + }) }) diff --git a/test/units/utils.ts b/test/units/utils.ts index a29eb2a03..47b27344e 100644 --- a/test/units/utils.ts +++ b/test/units/utils.ts @@ -103,10 +103,10 @@ export const mock = { } }) }, - textDocument: (filePath: string, content: string, params: { languageId?: string, isDirty?: boolean, isClosed?: boolean } = {}) => { + textDocument: (filePath: string, content: string, params: { languageId?: string, isDirty?: boolean, isClosed?: boolean, scheme?: string } = {}) => { return sinon.stub(vscode.workspace, 'textDocuments').value([ new TextDocument(filePath, content, params) ]) }, - activeTextEditor: (filePath: string, content: string, params: { languageId?: string, isDirty?: boolean, isClosed?: boolean } = {}) => { + activeTextEditor: (filePath: string, content: string, params: { languageId?: string, isDirty?: boolean, isClosed?: boolean, scheme?: string } = {}) => { return sinon.stub(vscode.window, 'activeTextEditor').value(new TextEditor(filePath, content, params)) } } @@ -135,10 +135,10 @@ class TextDocument implements vscode.TextDocument { eol: vscode.EndOfLine = vscode.EndOfLine.LF lineCount: number - constructor(filePath: string, content: string, { languageId = 'latex', isDirty = false, isClosed = false }: { languageId?: string, isDirty?: boolean, isClosed?: boolean }) { + constructor(filePath: string, content: string, { languageId = 'latex', isDirty = false, isClosed = false, scheme = 'file' }: { languageId?: string, isDirty?: boolean, isClosed?: boolean, scheme?: string }) { this.content = content this.lines = content.split('\n') - this.uri = vscode.Uri.file(filePath) + this.uri = scheme === 'file' ? vscode.Uri.file(filePath) : vscode.Uri.from({ scheme, path: filePath }) this.fileName = filePath this.languageId = languageId this.isDirty = isDirty @@ -163,8 +163,8 @@ class TextEditor implements vscode.TextEditor { options: vscode.TextEditorOptions = {} viewColumn: vscode.ViewColumn | undefined = vscode.ViewColumn.Active - constructor(filePath: string, content: string, { languageId = 'latex', isDirty = false, isClosed = false }: { languageId?: string, isDirty?: boolean, isClosed?: boolean }) { - this.document = new TextDocument(filePath, content, { languageId, isDirty, isClosed }) + constructor(filePath: string, content: string, { languageId = 'latex', isDirty = false, isClosed = false, scheme = 'file' }: { languageId?: string, isDirty?: boolean, isClosed?: boolean, scheme?: string }) { + this.document = new TextDocument(filePath, content, { languageId, isDirty, isClosed, scheme }) } edit(_: (_: vscode.TextEditorEdit) => void): Thenable { throw new Error('Not implemented.') }