Merge from vscode merge-base (#22780)

* Revert "Revert "Merge from vscode merge-base (#22769)" (#22779)"

This reverts commit 47a1745180.

* Fix notebook download task

* Remove done call from extensions-ci
This commit is contained in:
Karl Burtram
2023-04-19 21:48:46 -07:00
committed by GitHub
parent decbe8dded
commit e7d3d047ec
2389 changed files with 92155 additions and 42602 deletions

View File

@@ -1,137 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdDefinitionProvider } from '../languageFeatures/definitionProvider';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdReferencesProvider } from '../languageFeatures/references';
import { githubSlugifier } from '../slugify';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { MdWorkspaceContents } from '../workspaceContents';
import { createNewMarkdownEngine } from './engine';
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
import { joinLines, noopToken, workspacePath } from './util';
function getDefinition(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents) {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
const provider = new MdDefinitionProvider(referencesProvider);
return provider.provideDefinition(doc, pos, noopToken);
}
function assertDefinitionsEqual(actualDef: vscode.Definition, ...expectedDefs: { uri: vscode.Uri; line: number; startCharacter?: number; endCharacter?: number }[]) {
const actualDefsArr = Array.isArray(actualDef) ? actualDef : [actualDef];
assert.strictEqual(actualDefsArr.length, expectedDefs.length, `Definition counts should match`);
for (let i = 0; i < actualDefsArr.length; ++i) {
const actual = actualDefsArr[i];
const expected = expectedDefs[i];
assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Definition '${i}' has expected document`);
assert.strictEqual(actual.range.start.line, expected.line, `Definition '${i}' has expected start line`);
assert.strictEqual(actual.range.end.line, expected.line, `Definition '${i}' has expected end line`);
if (typeof expected.startCharacter !== 'undefined') {
assert.strictEqual(actual.range.start.character, expected.startCharacter, `Definition '${i}' has expected start character`);
}
if (typeof expected.endCharacter !== 'undefined') {
assert.strictEqual(actual.range.end.character, expected.endCharacter, `Definition '${i}' has expected end character`);
}
}
}
suite('markdown: Go to definition', () => {
test('Should not return definition when on link text', async () => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`[ref](#abc)`,
`[ref]: http://example.com`,
));
const defs = await getDefinition(doc, new vscode.Position(0, 1), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(defs, undefined);
});
test('Should find definition links within file from link', async () => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`, // trigger here
``,
`[abc]: https://example.com`,
));
const defs = await getDefinition(doc, new vscode.Position(0, 12), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 2 },
);
});
test('Should find definition links using shorthand', async () => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[ref]`, // trigger 1
``,
`[yes][ref]`, // trigger 2
``,
`[ref]: /Hello.md` // trigger 3
));
{
const defs = await getDefinition(doc, new vscode.Position(0, 2), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 4 },
);
}
{
const defs = await getDefinition(doc, new vscode.Position(2, 7), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 4 },
);
}
{
const defs = await getDefinition(doc, new vscode.Position(4, 2), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 4 },
);
}
});
test('Should find definition links within file from definition', async () => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`,
``,
`[abc]: https://example.com`, // trigger here
));
const defs = await getDefinition(doc, new vscode.Position(2, 3), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 2 },
);
});
test('Should not find definition links across files', async () => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`,
``,
`[abc]: https://example.com`,
));
const defs = await getDefinition(doc, new vscode.Position(0, 12), new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(workspacePath('other.md'), joinLines(
`[link 1][abc]`,
``,
`[abc]: https://example.com?bad`,
))
]));
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 2 },
);
});
});

View File

@@ -1,160 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as vscode from 'vscode';
import 'mocha';
import { DiagnosticComputer, DiagnosticConfiguration, DiagnosticLevel, DiagnosticManager, DiagnosticOptions } from '../languageFeatures/diagnostics';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { MdWorkspaceContents } from '../workspaceContents';
import { createNewMarkdownEngine } from './engine';
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
import { assertRangeEqual, joinLines, noopToken, workspacePath } from './util';
function getComputedDiagnostics(doc: InMemoryDocument, workspaceContents: MdWorkspaceContents) {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const computer = new DiagnosticComputer(engine, workspaceContents, linkProvider);
return computer.getDiagnostics(doc, {
enabled: true,
validateFilePaths: DiagnosticLevel.warning,
validateOwnHeaders: DiagnosticLevel.warning,
validateReferences: DiagnosticLevel.warning,
}, noopToken);
}
function createDiagnosticsManager(workspaceContents: MdWorkspaceContents, configuration = new MemoryDiagnosticConfiguration()) {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
return new DiagnosticManager(new DiagnosticComputer(engine, workspaceContents, linkProvider), configuration);
}
class MemoryDiagnosticConfiguration implements DiagnosticConfiguration {
private readonly _onDidChange = new vscode.EventEmitter<void>();
public readonly onDidChange = this._onDidChange.event;
constructor(
private readonly enabled: boolean = true,
) { }
getOptions(_resource: vscode.Uri): DiagnosticOptions {
if (!this.enabled) {
return {
enabled: false,
validateFilePaths: DiagnosticLevel.ignore,
validateOwnHeaders: DiagnosticLevel.ignore,
validateReferences: DiagnosticLevel.ignore,
};
}
return {
enabled: true,
validateFilePaths: DiagnosticLevel.warning,
validateOwnHeaders: DiagnosticLevel.warning,
validateReferences: DiagnosticLevel.warning,
};
}
}
suite('markdown: Diagnostics', () => {
test('Should not return any diagnostics for empty document', async () => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`text`,
));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(diagnostics, []);
});
test('Should generate diagnostic for link to file that does not exist', async () => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`[bad](/no/such/file.md)`,
`[good](/doc.md)`,
`[good-ref]: /doc.md`,
`[bad-ref]: /no/such/file.md`,
));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(diagnostics.length, 2);
assertRangeEqual(new vscode.Range(0, 6, 0, 22), diagnostics[0].range);
assertRangeEqual(new vscode.Range(3, 11, 3, 27), diagnostics[1].range);
});
test('Should generate diagnostics for links to header that does not exist in current file', async () => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`[good](#good-header)`,
`# Good Header`,
`[bad](#no-such-header)`,
`[good](#good-header)`,
`[good-ref]: #good-header`,
`[bad-ref]: #no-such-header`,
));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(diagnostics.length, 2);
assertRangeEqual(new vscode.Range(2, 6, 2, 21), diagnostics[0].range);
assertRangeEqual(new vscode.Range(5, 11, 5, 26), diagnostics[1].range);
});
test('Should generate diagnostics for links to non-existent headers in other files', async () => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`# My header`,
`[good](#my-header)`,
`[good](/doc1.md#my-header)`,
`[good](doc1.md#my-header)`,
`[good](/doc2.md#other-header)`,
`[bad](/doc2.md#no-such-other-header)`,
));
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(
`# Other header`,
));
const diagnostics = await getComputedDiagnostics(doc1, new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]));
assert.deepStrictEqual(diagnostics.length, 1);
assertRangeEqual(new vscode.Range(5, 6, 5, 35), diagnostics[0].range);
});
test('Should support links both with and without .md file extension', async () => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`# My header`,
`[good](#my-header)`,
`[good](/doc.md#my-header)`,
`[good](doc.md#my-header)`,
`[good](/doc#my-header)`,
`[good](doc#my-header)`,
));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(diagnostics.length, 0);
});
test('Should generate diagnostics for non-existent link reference', async () => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`[good link][good]`,
`[bad link][no-such]`,
``,
`[good]: http://example.com`,
));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(diagnostics.length, 1);
assertRangeEqual(new vscode.Range(1, 11, 1, 18), diagnostics[0].range);
});
test('Should not generate diagnostics when validate is disabled', async () => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`[text](#no-such-header)`,
`[text][no-such-ref]`,
));
const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(false));
const diagnostics = await manager.getDiagnostics(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
});
});

View File

@@ -24,7 +24,7 @@ function workspaceFile(...segments: string[]) {
async function getLinksForFile(file: vscode.Uri): Promise<vscode.DocumentLink[]> {
debugLog('getting links', file.toString(), Date.now());
const r = (await vscode.commands.executeCommand<vscode.DocumentLink[]>('vscode.executeLinkProvider', file))!;
const r = (await vscode.commands.executeCommand<vscode.DocumentLink[]>('vscode.executeLinkProvider', file, /*linkResolveCount*/ 100))!;
debugLog('got links', file.toString(), Date.now());
return r;
}
@@ -134,7 +134,7 @@ async function getLinksForFile(file: vscode.Uri): Promise<vscode.DocumentLink[]>
}
});
test('Should navigate to fragment within current untitled file', async () => {
test('Should navigate to fragment within current untitled file', async () => { // TODO: skip for now for ls migration
const testFile = workspaceFile('x.md').with({ scheme: 'untitled' });
await withFileContents(testFile, joinLines(
'[](#second)',
@@ -171,7 +171,7 @@ async function withFileContents(file: vscode.Uri, contents: string): Promise<voi
async function executeLink(link: vscode.DocumentLink) {
debugLog('executeingLink', link.target?.toString(), Date.now());
const args = JSON.parse(decodeURIComponent(link.target!.query));
await vscode.commands.executeCommand(link.target!.path, args);
const args: any[] = JSON.parse(decodeURIComponent(link.target!.query));
await vscode.commands.executeCommand(link.target!.path, vscode.Uri.from(args[0]), ...args.slice(1));
debugLog('executedLink', vscode.window.activeTextEditor?.document.toString(), Date.now());
}

View File

@@ -1,270 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { assertRangeEqual, joinLines, noopToken } from './util';
const testFile = vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, 'x.md');
function getLinksForFile(fileContents: string) {
const doc = new InMemoryDocument(testFile, fileContents);
const provider = new MdLinkProvider(createNewMarkdownEngine());
return provider.provideDocumentLinks(doc, noopToken);
}
suite('markdown.DocumentLinkProvider', () => {
test('Should not return anything for empty document', async () => {
const links = await getLinksForFile('');
assert.strictEqual(links.length, 0);
});
test('Should not return anything for simple document without links', async () => {
const links = await getLinksForFile('# a\nfdasfdfsafsa');
assert.strictEqual(links.length, 0);
});
test('Should detect basic http links', async () => {
const links = await getLinksForFile('a [b](https://example.com) c');
assert.strictEqual(links.length, 1);
const [link] = links;
assertRangeEqual(link.range, new vscode.Range(0, 6, 0, 25));
});
test('Should detect basic workspace links', async () => {
{
const links = await getLinksForFile('a [b](./file) c');
assert.strictEqual(links.length, 1);
const [link] = links;
assertRangeEqual(link.range, new vscode.Range(0, 6, 0, 12));
}
{
const links = await getLinksForFile('a [b](file.png) c');
assert.strictEqual(links.length, 1);
const [link] = links;
assertRangeEqual(link.range, new vscode.Range(0, 6, 0, 14));
}
});
test('Should detect links with title', async () => {
const links = await getLinksForFile('a [b](https://example.com "abc") c');
assert.strictEqual(links.length, 1);
const [link] = links;
assertRangeEqual(link.range, new vscode.Range(0, 6, 0, 25));
});
// #35245
test('Should handle links with escaped characters in name', async () => {
const links = await getLinksForFile('a [b\\]](./file)');
assert.strictEqual(links.length, 1);
const [link] = links;
assertRangeEqual(link.range, new vscode.Range(0, 8, 0, 14));
});
test('Should handle links with balanced parens', async () => {
{
const links = await getLinksForFile('a [b](https://example.com/a()c) c');
assert.strictEqual(links.length, 1);
const [link] = links;
assertRangeEqual(link.range, new vscode.Range(0, 6, 0, 30));
}
{
const links = await getLinksForFile('a [b](https://example.com/a(b)c) c');
assert.strictEqual(links.length, 1);
const [link] = links;
assertRangeEqual(link.range, new vscode.Range(0, 6, 0, 31));
}
{
// #49011
const links = await getLinksForFile('[A link](http://ThisUrlhasParens/A_link(in_parens))');
assert.strictEqual(links.length, 1);
const [link] = links;
assertRangeEqual(link.range, new vscode.Range(0, 9, 0, 50));
}
});
test('Should handle two links without space', async () => {
const links = await getLinksForFile('a ([test](test)[test2](test2)) c');
assert.strictEqual(links.length, 2);
const [link1, link2] = links;
assertRangeEqual(link1.range, new vscode.Range(0, 10, 0, 14));
assertRangeEqual(link2.range, new vscode.Range(0, 23, 0, 28));
});
// #49238
test('should handle hyperlinked images', async () => {
{
const links = await getLinksForFile('[![alt text](image.jpg)](https://example.com)');
assert.strictEqual(links.length, 2);
const [link1, link2] = links;
assertRangeEqual(link1.range, new vscode.Range(0, 13, 0, 22));
assertRangeEqual(link2.range, new vscode.Range(0, 25, 0, 44));
}
{
const links = await getLinksForFile('[![a]( whitespace.jpg )]( https://whitespace.com )');
assert.strictEqual(links.length, 2);
const [link1, link2] = links;
assertRangeEqual(link1.range, new vscode.Range(0, 7, 0, 21));
assertRangeEqual(link2.range, new vscode.Range(0, 26, 0, 48));
}
{
const links = await getLinksForFile('[![a](img1.jpg)](file1.txt) text [![a](img2.jpg)](file2.txt)');
assert.strictEqual(links.length, 4);
const [link1, link2, link3, link4] = links;
assertRangeEqual(link1.range, new vscode.Range(0, 6, 0, 14));
assertRangeEqual(link2.range, new vscode.Range(0, 17, 0, 26));
assertRangeEqual(link3.range, new vscode.Range(0, 39, 0, 47));
assertRangeEqual(link4.range, new vscode.Range(0, 50, 0, 59));
}
});
test('Should not consider link references starting with ^ character valid (#107471)', async () => {
const links = await getLinksForFile('[^reference]: https://example.com');
assert.strictEqual(links.length, 0);
});
test('Should find definitions links with spaces in angle brackets (#136073)', async () => {
const links = await getLinksForFile([
'[a]: <b c>',
'[b]: <cd>',
].join('\n'));
assert.strictEqual(links.length, 2);
const [link1, link2] = links;
assertRangeEqual(link1.range, new vscode.Range(0, 6, 0, 9));
assertRangeEqual(link2.range, new vscode.Range(1, 6, 1, 8));
});
test('Should only find one link for reference sources [a]: source (#141285)', async () => {
const links = await getLinksForFile([
'[Works]: https://microsoft.com',
].join('\n'));
assert.strictEqual(links.length, 1);
});
test('Should find links for referees with only one [] (#141285)', async () => {
let links = await getLinksForFile([
'[ref]',
'[ref]: https://microsoft.com',
].join('\n'));
assert.strictEqual(links.length, 2);
links = await getLinksForFile([
'[Does Not Work]',
'[def]: https://microsoft.com',
].join('\n'));
assert.strictEqual(links.length, 1);
});
test('Should not find link for reference using one [] when source does not exist (#141285)', async () => {
const links = await getLinksForFile('[Works]');
assert.strictEqual(links.length, 0);
});
test('Should not consider links in code fenced with backticks', async () => {
const text = joinLines(
'```',
'[b](https://example.com)',
'```');
const links = await getLinksForFile(text);
assert.strictEqual(links.length, 0);
});
test('Should not consider links in code fenced with tilda', async () => {
const text = joinLines(
'~~~',
'[b](https://example.com)',
'~~~');
const links = await getLinksForFile(text);
assert.strictEqual(links.length, 0);
});
test('Should not consider links in indented code', async () => {
const links = await getLinksForFile(' [b](https://example.com)');
assert.strictEqual(links.length, 0);
});
test('Should not consider links in inline code span', async () => {
const links = await getLinksForFile('`[b](https://example.com)`');
assert.strictEqual(links.length, 0);
});
test('Should not consider links with code span inside', async () => {
const links = await getLinksForFile('[li`nk](https://example.com`)');
assert.strictEqual(links.length, 0);
});
test('Should not consider links in multiline inline code span', async () => {
const text = joinLines(
'`` ',
'[b](https://example.com)',
'``');
const links = await getLinksForFile(text);
assert.strictEqual(links.length, 0);
});
test('Should not consider link references in code fenced with backticks (#146714)', async () => {
const text = joinLines(
'```',
'[a] [bb]',
'```');
const links = await getLinksForFile(text);
assert.strictEqual(links.length, 0);
});
test('Should not consider reference sources in code fenced with backticks (#146714)', async () => {
const text = joinLines(
'```',
'[a]: http://example.com;',
'[b]: <http://example.com>;',
'[c]: (http://example.com);',
'```');
const links = await getLinksForFile(text);
assert.strictEqual(links.length, 0);
});
test('Should not consider links in multiline inline code span between between text', async () => {
const text = joinLines(
'[b](https://1.com) `[b](https://2.com)',
'` [b](https://3.com)');
const links = await getLinksForFile(text);
assert.deepStrictEqual(links.map(l => l.target?.authority), ['1.com', '3.com']);
});
test('Should not consider links in multiline inline code span with new line after the first backtick', async () => {
const text = joinLines(
'`',
'[b](https://example.com)`');
const links = await getLinksForFile(text);
assert.strictEqual(links.length, 0);
});
test('Should not miss links in invalid multiline inline code span', async () => {
const text = joinLines(
'`` ',
'',
'[b](https://example.com)',
'',
'``');
const links = await getLinksForFile(text);
assert.strictEqual(links.length, 1);
});
test('Should find autolinks', async () => {
const links = await getLinksForFile('pre <http://example.com> post');
assert.strictEqual(links.length, 1);
const link = links[0];
assertRangeEqual(link.range, new vscode.Range(0, 5, 0, 23));
});
});

View File

@@ -1,97 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbolProvider';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from '../util/inMemoryDocument';
const testFileName = vscode.Uri.file('test.md');
function getSymbolsForFile(fileContents: string) {
const doc = new InMemoryDocument(testFileName, fileContents);
const provider = new MdDocumentSymbolProvider(createNewMarkdownEngine());
return provider.provideDocumentSymbols(doc);
}
suite('markdown.DocumentSymbolProvider', () => {
test('Should not return anything for empty document', async () => {
const symbols = await getSymbolsForFile('');
assert.strictEqual(symbols.length, 0);
});
test('Should not return anything for document with no headers', async () => {
const symbols = await getSymbolsForFile('a\na');
assert.strictEqual(symbols.length, 0);
});
test('Should not return anything for document with # but no real headers', async () => {
const symbols = await getSymbolsForFile('a#a\na#');
assert.strictEqual(symbols.length, 0);
});
test('Should return single symbol for single header', async () => {
const symbols = await getSymbolsForFile('# h');
assert.strictEqual(symbols.length, 1);
assert.strictEqual(symbols[0].name, '# h');
});
test('Should not care about symbol level for single header', async () => {
const symbols = await getSymbolsForFile('### h');
assert.strictEqual(symbols.length, 1);
assert.strictEqual(symbols[0].name, '### h');
});
test('Should put symbols of same level in flat list', async () => {
const symbols = await getSymbolsForFile('## h\n## h2');
assert.strictEqual(symbols.length, 2);
assert.strictEqual(symbols[0].name, '## h');
assert.strictEqual(symbols[1].name, '## h2');
});
test('Should nest symbol of level - 1 under parent', async () => {
const symbols = await getSymbolsForFile('# h\n## h2\n## h3');
assert.strictEqual(symbols.length, 1);
assert.strictEqual(symbols[0].name, '# h');
assert.strictEqual(symbols[0].children.length, 2);
assert.strictEqual(symbols[0].children[0].name, '## h2');
assert.strictEqual(symbols[0].children[1].name, '## h3');
});
test('Should nest symbol of level - n under parent', async () => {
const symbols = await getSymbolsForFile('# h\n#### h2');
assert.strictEqual(symbols.length, 1);
assert.strictEqual(symbols[0].name, '# h');
assert.strictEqual(symbols[0].children.length, 1);
assert.strictEqual(symbols[0].children[0].name, '#### h2');
});
test('Should flatten children where lower level occurs first', async () => {
const symbols = await getSymbolsForFile('# h\n### h2\n## h3');
assert.strictEqual(symbols.length, 1);
assert.strictEqual(symbols[0].name, '# h');
assert.strictEqual(symbols[0].children.length, 2);
assert.strictEqual(symbols[0].children[0].name, '### h2');
assert.strictEqual(symbols[0].children[1].name, '## h3');
});
test('Should handle line separator in file. Issue #63749', async () => {
const symbols = await getSymbolsForFile(`# A
- foo
# B
- bar`);
assert.strictEqual(symbols.length, 2);
assert.strictEqual(symbols[0].name, '# A');
assert.strictEqual(symbols[1].name, '# B');
});
});

View File

@@ -6,8 +6,8 @@
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { createNewMarkdownEngine } from './engine';
const testFileName = vscode.Uri.file('test.md');

View File

@@ -4,10 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { MarkdownItEngine } from '../markdownEngine';
import { MarkdownContributionProvider, MarkdownContributions } from '../markdownExtensions';
import { githubSlugifier } from '../slugify';
import { Disposable } from '../util/dispose';
import { nulLogger } from './nulLogging';
const emptyContributions = new class extends Disposable implements MarkdownContributionProvider {
readonly extensionUri = vscode.Uri.file('/');
@@ -15,6 +16,6 @@ const emptyContributions = new class extends Disposable implements MarkdownContr
readonly onContributionsChanged = this._register(new vscode.EventEmitter<this>()).event;
};
export function createNewMarkdownEngine(): MarkdownEngine {
return new MarkdownEngine(emptyContributions, githubSlugifier);
export function createNewMarkdownEngine(): MarkdownItEngine {
return new MarkdownItEngine(emptyContributions, githubSlugifier, nulLogger);
}

View File

@@ -1,118 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdReference, MdReferencesProvider } from '../languageFeatures/references';
import { githubSlugifier } from '../slugify';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { MdWorkspaceContents } from '../workspaceContents';
import { createNewMarkdownEngine } from './engine';
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
import { joinLines, noopToken, workspacePath } from './util';
function getFileReferences(resource: vscode.Uri, workspaceContents: MdWorkspaceContents) {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const provider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
return provider.getAllReferencesToFile(resource, noopToken);
}
function assertReferencesEqual(actualRefs: readonly MdReference[], ...expectedRefs: { uri: vscode.Uri; line: number }[]) {
assert.strictEqual(actualRefs.length, expectedRefs.length, `Reference counts should match`);
for (let i = 0; i < actualRefs.length; ++i) {
const actual = actualRefs[i].location;
const expected = expectedRefs[i];
assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Ref '${i}' has expected document`);
assert.strictEqual(actual.range.start.line, expected.line, `Ref '${i}' has expected start line`);
assert.strictEqual(actual.range.end.line, expected.line, `Ref '${i}' has expected end line`);
}
}
suite('markdown: find file references', () => {
test('Should find basic references', async () => {
const docUri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([
new InMemoryDocument(docUri, joinLines(
`# header`,
`[link 1](./other.md)`,
`[link 2](./other.md)`,
)),
new InMemoryDocument(otherUri, joinLines(
`# header`,
`pre`,
`[link 3](./other.md)`,
`post`,
)),
]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 1 },
{ uri: docUri, line: 2 },
{ uri: otherUri, line: 2 },
);
});
test('Should find references with and without file extensions', async () => {
const docUri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([
new InMemoryDocument(docUri, joinLines(
`# header`,
`[link 1](./other.md)`,
`[link 2](./other)`,
)),
new InMemoryDocument(otherUri, joinLines(
`# header`,
`pre`,
`[link 3](./other.md)`,
`[link 4](./other)`,
`post`,
)),
]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 1 },
{ uri: docUri, line: 2 },
{ uri: otherUri, line: 2 },
{ uri: otherUri, line: 3 },
);
});
test('Should find references with headers on links', async () => {
const docUri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([
new InMemoryDocument(docUri, joinLines(
`# header`,
`[link 1](./other.md#sub-bla)`,
`[link 2](./other#sub-bla)`,
)),
new InMemoryDocument(otherUri, joinLines(
`# header`,
`pre`,
`[link 3](./other.md#sub-bla)`,
`[link 4](./other#sub-bla)`,
`post`,
)),
]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 1 },
{ uri: docUri, line: 2 },
{ uri: otherUri, line: 2 },
{ uri: otherUri, line: 3 },
);
});
});

View File

@@ -1,223 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdFoldingProvider } from '../languageFeatures/foldingProvider';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { createNewMarkdownEngine } from './engine';
import { joinLines } from './util';
const testFileName = vscode.Uri.file('test.md');
suite('markdown.FoldingProvider', () => {
test('Should not return anything for empty document', async () => {
const folds = await getFoldsForDocument(``);
assert.strictEqual(folds.length, 0);
});
test('Should not return anything for document without headers', async () => {
const folds = await getFoldsForDocument(joinLines(
`a`,
`**b** afas`,
`a#b`,
`a`,
));
assert.strictEqual(folds.length, 0);
});
test('Should fold from header to end of document', async () => {
const folds = await getFoldsForDocument(joinLines(
`a`,
`# b`,
`c`,
`d`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
});
test('Should leave single newline before next header', async () => {
const folds = await getFoldsForDocument(joinLines(
``,
`# a`,
`x`,
``,
`# b`,
`y`,
));
assert.strictEqual(folds.length, 2);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 2);
});
test('Should collapse multiple newlines to single newline before next header', async () => {
const folds = await getFoldsForDocument(joinLines(
``,
`# a`,
`x`,
``,
``,
``,
`# b`,
`y`
));
assert.strictEqual(folds.length, 2);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 4);
});
test('Should not collapse if there is no newline before next header', async () => {
const folds = await getFoldsForDocument(joinLines(
``,
`# a`,
`x`,
`# b`,
`y`,
));
assert.strictEqual(folds.length, 2);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 2);
});
test('Should fold nested <!-- #region --> markers', async () => {
const folds = await getFoldsForDocument(joinLines(
`a`,
`<!-- #region -->`,
`b`,
`<!-- #region hello!-->`,
`b.a`,
`<!-- #endregion -->`,
`b`,
`<!-- #region: foo! -->`,
`b.b`,
`<!-- #endregion: foo -->`,
`b`,
`<!-- #endregion -->`,
`a`,
));
assert.strictEqual(folds.length, 3);
const [outer, first, second] = folds.sort((a, b) => a.start - b.start);
assert.strictEqual(outer.start, 1);
assert.strictEqual(outer.end, 11);
assert.strictEqual(first.start, 3);
assert.strictEqual(first.end, 5);
assert.strictEqual(second.start, 7);
assert.strictEqual(second.end, 9);
});
test('Should fold from list to end of document', async () => {
const folds = await getFoldsForDocument(joinLines(
`a`,
`- b`,
`c`,
`d`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
});
test('lists folds should span multiple lines of content', async () => {
const folds = await getFoldsForDocument(joinLines(
`a`,
`- This list item\n spans multiple\n lines.`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
});
test('List should leave single blankline before new element', async () => {
const folds = await getFoldsForDocument(joinLines(
`- a`,
`a`,
``,
``,
`b`
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 0);
assert.strictEqual(firstFold.end, 2);
});
test('Should fold fenced code blocks', async () => {
const folds = await getFoldsForDocument(joinLines(
`~~~ts`,
`a`,
`~~~`,
`b`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 0);
assert.strictEqual(firstFold.end, 2);
});
test('Should fold fenced code blocks with yaml front matter', async () => {
const folds = await getFoldsForDocument(joinLines(
`---`,
`title: bla`,
`---`,
``,
`~~~ts`,
`a`,
`~~~`,
``,
`a`,
`a`,
`b`,
`a`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 4);
assert.strictEqual(firstFold.end, 6);
});
test('Should fold html blocks', async () => {
const folds = await getFoldsForDocument(joinLines(
`x`,
`<div>`,
` fa`,
`</div>`,
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
});
test('Should fold html block comments', async () => {
const folds = await getFoldsForDocument(joinLines(
`x`,
`<!--`,
`fa`,
`-->`
));
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
assert.strictEqual(firstFold.kind, vscode.FoldingRangeKind.Comment);
});
});
async function getFoldsForDocument(contents: string) {
const doc = new InMemoryDocument(testFileName, contents);
const provider = new MdFoldingProvider(createNewMarkdownEngine());
return await provider.provideFoldingRanges(doc, {}, new vscode.CancellationTokenSource().token);
}

View File

@@ -4,58 +4,80 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as path from 'path';
import * as vscode from 'vscode';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
import { ITextDocument } from '../types/textDocument';
import { Disposable } from '../util/dispose';
import { ResourceMap } from '../util/resourceMap';
import { IMdWorkspace } from '../workspace';
export class InMemoryWorkspaceMarkdownDocuments implements MdWorkspaceContents {
private readonly _documents = new Map<string, SkinnyTextDocument>();
export class InMemoryMdWorkspace extends Disposable implements IMdWorkspace {
private readonly _documents = new ResourceMap<ITextDocument>(uri => uri.fsPath);
constructor(documents: SkinnyTextDocument[]) {
constructor(documents: ITextDocument[]) {
super();
for (const doc of documents) {
this._documents.set(this.getKey(doc.uri), doc);
this._documents.set(doc.uri, doc);
}
}
public async getAllMarkdownDocuments() {
public values() {
return Array.from(this._documents.values());
}
public async getMarkdownDocument(resource: vscode.Uri): Promise<SkinnyTextDocument | undefined> {
return this._documents.get(this.getKey(resource));
public async getAllMarkdownDocuments() {
return this.values();
}
public async getOrLoadMarkdownDocument(resource: vscode.Uri): Promise<ITextDocument | undefined> {
return this._documents.get(resource);
}
public hasMarkdownDocument(resolvedHrefPath: vscode.Uri): boolean {
return this._documents.has(resolvedHrefPath);
}
public async pathExists(resource: vscode.Uri): Promise<boolean> {
return this._documents.has(this.getKey(resource));
return this._documents.has(resource);
}
private readonly _onDidChangeMarkdownDocumentEmitter = new vscode.EventEmitter<SkinnyTextDocument>();
public async readDirectory(resource: vscode.Uri): Promise<[string, vscode.FileType][]> {
const files = new Map<string, vscode.FileType>();
const pathPrefix = resource.fsPath + (resource.fsPath.endsWith('/') || resource.fsPath.endsWith('\\') ? '' : path.sep);
for (const doc of this._documents.values()) {
const path = doc.uri.fsPath;
if (path.startsWith(pathPrefix)) {
const parts = path.slice(pathPrefix.length).split(/\/|\\/g);
files.set(parts[0], parts.length > 1 ? vscode.FileType.Directory : vscode.FileType.File);
}
}
return Array.from(files.entries());
}
private readonly _onDidChangeMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<ITextDocument>());
public onDidChangeMarkdownDocument = this._onDidChangeMarkdownDocumentEmitter.event;
private readonly _onDidCreateMarkdownDocumentEmitter = new vscode.EventEmitter<SkinnyTextDocument>();
private readonly _onDidCreateMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<ITextDocument>());
public onDidCreateMarkdownDocument = this._onDidCreateMarkdownDocumentEmitter.event;
private readonly _onDidDeleteMarkdownDocumentEmitter = new vscode.EventEmitter<vscode.Uri>();
private readonly _onDidDeleteMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<vscode.Uri>());
public onDidDeleteMarkdownDocument = this._onDidDeleteMarkdownDocumentEmitter.event;
public updateDocument(document: SkinnyTextDocument) {
this._documents.set(this.getKey(document.uri), document);
public updateDocument(document: ITextDocument) {
this._documents.set(document.uri, document);
this._onDidChangeMarkdownDocumentEmitter.fire(document);
}
public createDocument(document: SkinnyTextDocument) {
assert.ok(!this._documents.has(this.getKey(document.uri)));
public createDocument(document: ITextDocument) {
assert.ok(!this._documents.has(document.uri));
this._documents.set(this.getKey(document.uri), document);
this._documents.set(document.uri, document);
this._onDidCreateMarkdownDocumentEmitter.fire(document);
}
public deleteDocument(resource: vscode.Uri) {
this._documents.delete(this.getKey(resource));
this._documents.delete(resource);
this._onDidDeleteMarkdownDocumentEmitter.fire(resource);
}
private getKey(resource: vscode.Uri): string {
return resource.fsPath;
}
}

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ILogger } from '../logging';
export const nulLogger = new class implements ILogger {
verbose(): void {
// noop
}
};

View File

@@ -1,169 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdPathCompletionProvider } from '../languageFeatures/pathCompletions';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { createNewMarkdownEngine } from './engine';
import { CURSOR, getCursorPositions, joinLines, noopToken, workspacePath } from './util';
function getCompletionsAtCursor(resource: vscode.Uri, fileContents: string) {
const doc = new InMemoryDocument(resource, fileContents);
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const provider = new MdPathCompletionProvider(engine, linkProvider);
const cursorPositions = getCursorPositions(fileContents, doc);
return provider.provideCompletionItems(doc, cursorPositions[0], noopToken, {
triggerCharacter: undefined,
triggerKind: vscode.CompletionTriggerKind.Invoke,
});
}
suite('Markdown path completion provider', () => {
setup(async () => {
// These tests assume that the markdown completion provider is already registered
await vscode.extensions.getExtension('vscode.markdown-language-features')!.activate();
});
test('Should not return anything when triggered in empty doc', async () => {
const completions = await getCompletionsAtCursor(workspacePath('new.md'), `${CURSOR}`);
assert.strictEqual(completions.length, 0);
});
test('Should return anchor completions', async () => {
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](#${CURSOR}`,
``,
`# A b C`,
`# x y Z`,
));
assert.strictEqual(completions.length, 2);
assert.ok(completions.some(x => x.label === '#a-b-c'), 'Has a-b-c anchor completion');
assert.ok(completions.some(x => x.label === '#x-y-z'), 'Has x-y-z anchor completion');
});
test('Should not return suggestions for http links', async () => {
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](http:${CURSOR}`,
``,
`# http`,
`# http:`,
`# https:`,
));
assert.strictEqual(completions.length, 0);
});
test('Should return relative path suggestions', async () => {
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](${CURSOR}`,
``,
`# A b C`,
));
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
});
test('Should return relative path suggestions using ./', async () => {
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](./${CURSOR}`,
``,
`# A b C`,
));
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
});
test('Should return absolute path suggestions using /', async () => {
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
`[](/${CURSOR}`,
``,
`# A b C`,
));
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
assert.ok(!completions.some(x => x.label === 'c.md'), 'Should not have c.md from sub folder');
});
test('Should return anchor suggestions in other file', async () => {
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
`[](/b.md#${CURSOR}`,
));
assert.ok(completions.some(x => x.label === '#b'), 'Has #b header completion');
assert.ok(completions.some(x => x.label === '#header1'), 'Has #header1 header completion');
});
test('Should reference links for current file', async () => {
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
`[][${CURSOR}`,
``,
`[ref-1]: bla`,
`[ref-2]: bla`,
));
assert.strictEqual(completions.length, 2);
assert.ok(completions.some(x => x.label === 'ref-1'), 'Has ref-1 reference completion');
assert.ok(completions.some(x => x.label === 'ref-2'), 'Has ref-2 reference completion');
});
test('Should complete headers in link definitions', async () => {
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
`# a B c`,
`# x y Z`,
`[ref-1]: ${CURSOR}`,
));
assert.ok(completions.some(x => x.label === '#a-b-c'), 'Has #a-b-c header completion');
assert.ok(completions.some(x => x.label === '#x-y-z'), 'Has #x-y-z header completion');
});
test('Should complete relative paths in link definitions', async () => {
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`# a B c`,
`[ref-1]: ${CURSOR}`,
));
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
});
test('Should escape spaces in path names', async () => {
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](./sub/${CURSOR})`
));
assert.ok(completions.some(x => x.insertText === 'file%20with%20space.md'), 'Has encoded path completion');
});
test('Should complete paths for path with encoded spaces', async () => {
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](./sub%20with%20space/${CURSOR})`
));
assert.ok(completions.some(x => x.insertText === 'file.md'), 'Has file from space');
});
test('Should complete definition path for path with encoded spaces', async () => {
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[def]: ./sub%20with%20space/${CURSOR}`
));
assert.ok(completions.some(x => x.insertText === 'file.md'), 'Has file from space');
});
});

View File

@@ -1,580 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdReferencesProvider } from '../languageFeatures/references';
import { githubSlugifier } from '../slugify';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { MdWorkspaceContents } from '../workspaceContents';
import { createNewMarkdownEngine } from './engine';
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
import { joinLines, noopToken, workspacePath } from './util';
function getReferences(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents) {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const provider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
return provider.provideReferences(doc, pos, { includeDeclaration: true }, noopToken);
}
function assertReferencesEqual(actualRefs: readonly vscode.Location[], ...expectedRefs: { uri: vscode.Uri; line: number; startCharacter?: number; endCharacter?: number }[]) {
assert.strictEqual(actualRefs.length, expectedRefs.length, `Reference counts should match`);
for (let i = 0; i < actualRefs.length; ++i) {
const actual = actualRefs[i];
const expected = expectedRefs[i];
assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Ref '${i}' has expected document`);
assert.strictEqual(actual.range.start.line, expected.line, `Ref '${i}' has expected start line`);
assert.strictEqual(actual.range.end.line, expected.line, `Ref '${i}' has expected end line`);
if (typeof expected.startCharacter !== 'undefined') {
assert.strictEqual(actual.range.start.character, expected.startCharacter, `Ref '${i}' has expected start character`);
}
if (typeof expected.endCharacter !== 'undefined') {
assert.strictEqual(actual.range.end.character, expected.endCharacter, `Ref '${i}' has expected end character`);
}
}
}
suite('markdown: find all references', () => {
test('Should not return references when not on header or link', async () => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`# abc`,
``,
`[link 1](#abc)`,
`text`,
));
{
const refs = await getReferences(doc, new vscode.Position(1, 0), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(refs, []);
}
{
const refs = await getReferences(doc, new vscode.Position(3, 2), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(refs, []);
}
});
test('Should find references from header within same file', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# abc`,
``,
`[link 1](#abc)`,
`[not link](#noabc)`,
`[link 2](#abc)`,
));
const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri, line: 0 },
{ uri, line: 2 },
{ uri, line: 4 },
);
});
test('Should not return references when on link text', async () => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`[ref](#abc)`,
`[ref]: http://example.com`,
));
const refs = await getReferences(doc, new vscode.Position(0, 1), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(refs, []);
});
test('Should find references using normalized slug', async () => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`# a B c`,
`[simple](#a-b-c)`,
`[start underscore](#_a-b-c)`,
`[different case](#a-B-C)`,
));
{
// Trigger header
const refs = await getReferences(doc, new vscode.Position(0, 0), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(refs!.length, 4);
}
{
// Trigger on line 1
const refs = await getReferences(doc, new vscode.Position(1, 12), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(refs!.length, 4);
}
{
// Trigger on line 2
const refs = await getReferences(doc, new vscode.Position(2, 24), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(refs!.length, 4);
}
{
// Trigger on line 3
const refs = await getReferences(doc, new vscode.Position(3, 20), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.deepStrictEqual(refs!.length, 4);
}
});
test('Should find references from header across files', async () => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('sub', 'other.md');
const other2Uri = workspacePath('other2.md');
const doc = new InMemoryDocument(docUri, joinLines(
`# abc`,
``,
`[link 1](#abc)`,
));
const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(other1Uri, joinLines(
`[not link](#abc)`,
`[not link](/doc.md#abz)`,
`[link](/doc.md#abc)`,
)),
new InMemoryDocument(other2Uri, joinLines(
`[not link](#abc)`,
`[not link](./doc.md#abz)`,
`[link](./doc.md#abc)`,
))
]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 }, // Header definition
{ uri: docUri, line: 2 },
{ uri: other1Uri, line: 2 },
{ uri: other2Uri, line: 2 },
);
});
test('Should find references from header to link definitions ', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# abc`,
``,
`[bla]: #abc`
));
const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri, line: 0 }, // Header definition
{ uri, line: 2 },
);
});
test('Should find header references from link definition', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# A b C`,
`[text][bla]`,
`[bla]: #a-b-c`, // trigger here
));
const refs = await getReferences(doc, new vscode.Position(2, 9), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri, line: 0 }, // Header definition
{ uri, line: 2 },
);
});
test('Should find references from link within same file', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# abc`,
``,
`[link 1](#abc)`,
`[not link](#noabc)`,
`[link 2](#abc)`,
));
const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri, line: 0 }, // Header definition
{ uri, line: 2 },
{ uri, line: 4 },
);
});
test('Should find references from link across files', async () => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('sub', 'other.md');
const other2Uri = workspacePath('other2.md');
const doc = new InMemoryDocument(docUri, joinLines(
`# abc`,
``,
`[link 1](#abc)`,
));
const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(other1Uri, joinLines(
`[not link](#abc)`,
`[not link](/doc.md#abz)`,
`[with ext](/doc.md#abc)`,
`[without ext](/doc#abc)`,
)),
new InMemoryDocument(other2Uri, joinLines(
`[not link](#abc)`,
`[not link](./doc.md#abz)`,
`[link](./doc.md#abc)`,
))
]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 }, // Header definition
{ uri: docUri, line: 2 },
{ uri: other1Uri, line: 2 }, // Other with ext
{ uri: other1Uri, line: 3 }, // Other without ext
{ uri: other2Uri, line: 2 }, // Other2
);
});
test('Should find references without requiring file extensions', async () => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('other.md');
const doc = new InMemoryDocument(docUri, joinLines(
`# a B c`,
``,
`[link 1](#a-b-c)`,
));
const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(other1Uri, joinLines(
`[not link](#a-b-c)`,
`[not link](/doc.md#a-b-z)`,
`[with ext](/doc.md#a-b-c)`,
`[without ext](/doc#a-b-c)`,
`[rel with ext](./doc.md#a-b-c)`,
`[rel without ext](./doc#a-b-c)`,
)),
]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 }, // Header definition
{ uri: docUri, line: 2 },
{ uri: other1Uri, line: 2 }, // Other with ext
{ uri: other1Uri, line: 3 }, // Other without ext
{ uri: other1Uri, line: 4 }, // Other relative link with ext
{ uri: other1Uri, line: 5 }, // Other relative link without ext
);
});
test('Should find references from link across files when triggered on link without file extension', async () => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('sub', 'other.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[with ext](./sub/other#header)`,
`[without ext](./sub/other.md#header)`,
));
const refs = await getReferences(doc, new vscode.Position(0, 23), new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(other1Uri, joinLines(
`pre`,
`# header`,
`post`,
)),
]));
assertReferencesEqual(refs!,
{ uri: other1Uri, line: 1 }, // Header definition
{ uri: docUri, line: 0 },
{ uri: docUri, line: 1 },
);
});
test('Should include header references when triggered on file link', async () => {
const docUri = workspacePath('doc.md');
const otherUri = workspacePath('sub', 'other.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[with ext](./sub/other)`,
`[with ext](./sub/other#header)`,
`[without ext](./sub/other.md#no-such-header)`,
));
const refs = await getReferences(doc, new vscode.Position(0, 15), new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(otherUri, joinLines(
`pre`,
`# header`, // Definition should not be included since we triggered on a file link
`post`,
)),
]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 1 },
{ uri: docUri, line: 2 },
);
});
test('Should not include refs from other file to own header', async () => {
const docUri = workspacePath('doc.md');
const otherUri = workspacePath('sub', 'other.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[other](./sub/other)`, // trigger here
));
const refs = await getReferences(doc, new vscode.Position(0, 15), new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(otherUri, joinLines(
`# header`, // Definition should not be included since we triggered on a file link
`[text](#header)`, // Ref should not be included since it is to own file
)),
]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
);
});
test('Should find explicit references to own file ', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[bare](doc.md)`, // trigger here
`[rel](./doc.md)`,
`[abs](/doc.md)`,
));
const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri, line: 0 },
{ uri, line: 1 },
{ uri, line: 2 },
);
});
test('Should support finding references to http uri', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[1](http://example.com)`,
`[no](https://example.com)`,
`[2](http://example.com)`,
`[3]: http://example.com`,
));
const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri, line: 0 },
{ uri, line: 2 },
{ uri, line: 3 },
);
});
test('Should consider authority, scheme and paths when finding references to http uri', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[1](http://example.com/cat)`,
`[2](http://example.com)`,
`[3](http://example.com/dog)`,
`[4](http://example.com/cat/looong)`,
`[5](http://example.com/cat)`,
`[6](http://other.com/cat)`,
`[7](https://example.com/cat)`,
));
const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri, line: 0 },
{ uri, line: 4 },
);
});
test('Should support finding references to http uri across files', async () => {
const uri1 = workspacePath('doc.md');
const uri2 = workspacePath('doc2.md');
const doc = new InMemoryDocument(uri1, joinLines(
`[1](http://example.com)`,
`[3]: http://example.com`,
));
const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(uri2, joinLines(
`[other](http://example.com)`,
))
]));
assertReferencesEqual(refs!,
{ uri: uri1, line: 0 },
{ uri: uri1, line: 1 },
{ uri: uri2, line: 0 },
);
});
test('Should support finding references to autolinked http links', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[1](http://example.com)`,
`<http://example.com>`,
));
const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri, line: 0 },
{ uri, line: 1 },
);
});
test('Should distinguish between references to file and to header within file', async () => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('sub', 'other.md');
const doc = new InMemoryDocument(docUri, joinLines(
`# abc`,
``,
`[link 1](#abc)`,
));
const otherDoc = new InMemoryDocument(other1Uri, joinLines(
`[link](/doc.md#abc)`,
`[link no text](/doc#abc)`,
));
const workspaceContents = new InMemoryWorkspaceMarkdownDocuments([
doc,
otherDoc,
]);
{
// Check refs to header fragment
const headerRefs = await getReferences(otherDoc, new vscode.Position(0, 16), workspaceContents);
assertReferencesEqual(headerRefs!,
{ uri: docUri, line: 0 }, // Header definition
{ uri: docUri, line: 2 },
{ uri: other1Uri, line: 0 },
{ uri: other1Uri, line: 1 },
);
}
{
// Check refs to file itself from link with ext
const fileRefs = await getReferences(otherDoc, new vscode.Position(0, 9), workspaceContents);
assertReferencesEqual(fileRefs!,
{ uri: other1Uri, line: 0, endCharacter: 14 },
{ uri: other1Uri, line: 1, endCharacter: 19 },
);
}
{
// Check refs to file itself from link without ext
const fileRefs = await getReferences(otherDoc, new vscode.Position(1, 17), workspaceContents);
assertReferencesEqual(fileRefs!,
{ uri: other1Uri, line: 0 },
{ uri: other1Uri, line: 1 },
);
}
});
test('Should support finding references to unknown file', async () => {
const uri1 = workspacePath('doc1.md');
const doc1 = new InMemoryDocument(uri1, joinLines(
`![img](/images/more/image.png)`,
``,
`[ref]: /images/more/image.png`,
));
const uri2 = workspacePath('sub', 'doc2.md');
const doc2 = new InMemoryDocument(uri2, joinLines(
`![img](/images/more/image.png)`,
));
const refs = await getReferences(doc1, new vscode.Position(0, 10), new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]));
assertReferencesEqual(refs!,
{ uri: uri1, line: 0 },
{ uri: uri1, line: 2 },
{ uri: uri2, line: 0 },
);
});
suite('Reference links', () => {
test('Should find reference links within file from link', async () => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`, // trigger here
``,
`[abc]: https://example.com`,
));
const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
);
});
test('Should find reference links using shorthand', async () => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[ref]`, // trigger 1
``,
`[yes][ref]`, // trigger 2
``,
`[ref]: /Hello.md` // trigger 3
));
{
const refs = await getReferences(doc, new vscode.Position(0, 2), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
{ uri: docUri, line: 4 },
);
}
{
const refs = await getReferences(doc, new vscode.Position(2, 7), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
{ uri: docUri, line: 4 },
);
}
{
const refs = await getReferences(doc, new vscode.Position(4, 2), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
{ uri: docUri, line: 4 },
);
}
});
test('Should find reference links within file from definition', async () => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`,
``,
`[abc]: https://example.com`, // trigger here
));
const refs = await getReferences(doc, new vscode.Position(2, 3), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
);
});
test('Should not find reference links across files', async () => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`,
``,
`[abc]: https://example.com`,
));
const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(workspacePath('other.md'), joinLines(
`[link 1][abc]`,
``,
`[abc]: https://example.com?bad`,
))
]));
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
);
});
});
});

View File

@@ -1,616 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdReferencesProvider } from '../languageFeatures/references';
import { MdRenameProvider, MdWorkspaceEdit } from '../languageFeatures/rename';
import { githubSlugifier } from '../slugify';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { MdWorkspaceContents } from '../workspaceContents';
import { createNewMarkdownEngine } from './engine';
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
import { assertRangeEqual, joinLines, noopToken, workspacePath } from './util';
/**
* Get prepare rename info.
*/
function prepareRename(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents): Promise<undefined | { readonly range: vscode.Range; readonly placeholder: string }> {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
const renameProvider = new MdRenameProvider(referencesProvider, workspaceContents, githubSlugifier);
return renameProvider.prepareRename(doc, pos, noopToken);
}
/**
* Get all the edits for the rename.
*/
function getRenameEdits(doc: InMemoryDocument, pos: vscode.Position, newName: string, workspaceContents: MdWorkspaceContents): Promise<MdWorkspaceEdit | undefined> {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
const renameProvider = new MdRenameProvider(referencesProvider, workspaceContents, githubSlugifier);
return renameProvider.provideRenameEditsImpl(doc, pos, newName, noopToken);
}
interface ExpectedTextEdit {
readonly uri: vscode.Uri;
readonly edits: readonly vscode.TextEdit[];
}
interface ExpectedFileRename {
readonly originalUri: vscode.Uri;
readonly newUri: vscode.Uri;
}
function assertEditsEqual(actualEdit: MdWorkspaceEdit, ...expectedEdits: ReadonlyArray<ExpectedTextEdit | ExpectedFileRename>) {
// Check file renames
const expectedFileRenames = expectedEdits.filter(expected => 'originalUri' in expected) as ExpectedFileRename[];
const actualFileRenames = actualEdit.fileRenames ?? [];
assert.strictEqual(actualFileRenames.length, expectedFileRenames.length, `File rename count should match`);
for (let i = 0; i < actualFileRenames.length; ++i) {
const expected = expectedFileRenames[i];
const actual = actualFileRenames[i];
assert.strictEqual(actual.from.toString(), expected.originalUri.toString(), `File rename '${i}' should have expected 'from' resource`);
assert.strictEqual(actual.to.toString(), expected.newUri.toString(), `File rename '${i}' should have expected 'to' resource`);
}
// Check text edits
const actualTextEdits = actualEdit.edit.entries();
const expectedTextEdits = expectedEdits.filter(expected => 'edits' in expected) as ExpectedTextEdit[];
assert.strictEqual(actualTextEdits.length, expectedTextEdits.length, `Reference counts should match`);
for (let i = 0; i < actualTextEdits.length; ++i) {
const expected = expectedTextEdits[i];
const actual = actualTextEdits[i];
if ('edits' in expected) {
assert.strictEqual(actual[0].toString(), expected.uri.toString(), `Ref '${i}' has expected document`);
const actualEditForDoc = actual[1];
const expectedEditsForDoc = expected.edits;
assert.strictEqual(actualEditForDoc.length, expectedEditsForDoc.length, `Edit counts for '${actual[0]}' should match`);
for (let g = 0; g < actualEditForDoc.length; ++g) {
assertRangeEqual(actualEditForDoc[g].range, expectedEditsForDoc[g].range, `Edit '${g}' of '${actual[0]}' has expected expected range. Expected range: ${JSON.stringify(actualEditForDoc[g].range)}. Actual range: ${JSON.stringify(expectedEditsForDoc[g].range)}`);
assert.strictEqual(actualEditForDoc[g].newText, expectedEditsForDoc[g].newText, `Edit '${g}' of '${actual[0]}' has expected edits`);
}
}
}
}
suite('markdown: rename', () => {
setup(async () => {
// the tests make the assumption that link providers are already registered
await vscode.extensions.getExtension('vscode.markdown-language-features')!.activate();
});
test('Rename on header should not include leading #', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# abc`
));
const info = await prepareRename(doc, new vscode.Position(0, 0), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertRangeEqual(info!.range, new vscode.Range(0, 2, 0, 5));
const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 2, 0, 5), 'New Header')
]
});
});
test('Rename on header should include leading or trailing #s', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`### abc ###`
));
const info = await prepareRename(doc, new vscode.Position(0, 0), new InMemoryWorkspaceMarkdownDocuments([doc]));
assertRangeEqual(info!.range, new vscode.Range(0, 4, 0, 7));
const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 7), 'New Header')
]
});
});
test('Rename on header should pick up links in doc', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`### A b C`, // rename here
`[text](#a-b-c)`,
));
const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
]
});
});
test('Rename on link should use slug for link', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`### A b C`,
`[text](#a-b-c)`, // rename here
));
const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
]
});
});
test('Rename on link definition should work', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`### A b C`,
`[text](#a-b-c)`,
`[ref]: #a-b-c`// rename here
));
const edit = await getRenameEdits(doc, new vscode.Position(2, 10), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
new vscode.TextEdit(new vscode.Range(2, 8, 2, 13), 'new-header'),
]
});
});
test('Rename on header should pick up links across files', async () => {
const uri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const doc = new InMemoryDocument(uri, joinLines(
`### A b C`, // rename here
`[text](#a-b-c)`,
));
const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(otherUri, joinLines(
`[text](#a-b-c)`, // Should not find this
`[text](./doc.md#a-b-c)`, // But should find this
`[text](./doc#a-b-c)`, // And this
))
]));
assertEditsEqual(edit!, {
uri: uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
]
}, {
uri: otherUri, edits: [
new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'),
new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'),
]
});
});
test('Rename on link should pick up links across files', async () => {
const uri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const doc = new InMemoryDocument(uri, joinLines(
`### A b C`,
`[text](#a-b-c)`, // rename here
));
const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "New Header", new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(otherUri, joinLines(
`[text](#a-b-c)`, // Should not find this
`[text](./doc.md#a-b-c)`, // But should find this
`[text](./doc#a-b-c)`, // And this
))
]));
assertEditsEqual(edit!, {
uri: uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
]
}, {
uri: otherUri, edits: [
new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'),
new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'),
]
});
});
test('Rename on link in other file should pick up all refs', async () => {
const uri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const doc = new InMemoryDocument(uri, joinLines(
`### A b C`,
`[text](#a-b-c)`,
));
const otherDoc = new InMemoryDocument(otherUri, joinLines(
`[text](#a-b-c)`,
`[text](./doc.md#a-b-c)`,
`[text](./doc#a-b-c)`
));
const expectedEdits = [
{
uri: uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
]
}, {
uri: otherUri, edits: [
new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'),
new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'),
]
}
];
{
// Rename on header with file extension
const edit = await getRenameEdits(otherDoc, new vscode.Position(1, 17), "New Header", new InMemoryWorkspaceMarkdownDocuments([
doc,
otherDoc
]));
assertEditsEqual(edit!, ...expectedEdits);
}
{
// Rename on header without extension
const edit = await getRenameEdits(otherDoc, new vscode.Position(2, 15), "New Header", new InMemoryWorkspaceMarkdownDocuments([
doc,
otherDoc
]));
assertEditsEqual(edit!, ...expectedEdits);
}
});
test('Rename on reference should rename references and definition', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text][ref]`, // rename here
`[other][ref]`,
``,
`[ref]: https://example.com`,
));
const edit = await getRenameEdits(doc, new vscode.Position(0, 8), "new ref", new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'),
new vscode.TextEdit(new vscode.Range(1, 8, 1, 11), 'new ref'),
new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'),
]
});
});
test('Rename on definition should rename references and definitions', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text][ref]`,
`[other][ref]`,
``,
`[ref]: https://example.com`, // rename here
));
const edit = await getRenameEdits(doc, new vscode.Position(3, 3), "new ref", new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'),
new vscode.TextEdit(new vscode.Range(1, 8, 1, 11), 'new ref'),
new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'),
]
});
});
test('Rename on definition entry should rename header and references', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# a B c`,
`[ref text][ref]`,
`[direct](#a-b-c)`,
`[ref]: #a-b-c`, // rename here
));
const preparedInfo = await prepareRename(doc, new vscode.Position(3, 10), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.strictEqual(preparedInfo!.placeholder, 'a B c');
assertRangeEqual(preparedInfo!.range, new vscode.Range(3, 8, 3, 13));
const edit = await getRenameEdits(doc, new vscode.Position(3, 10), "x Y z", new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 2, 0, 7), 'x Y z'),
new vscode.TextEdit(new vscode.Range(2, 10, 2, 15), 'x-y-z'),
new vscode.TextEdit(new vscode.Range(3, 8, 3, 13), 'x-y-z'),
]
});
});
test('Rename should not be supported on link text', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# Header`,
`[text](#header)`,
));
await assert.rejects(prepareRename(doc, new vscode.Position(1, 2), new InMemoryWorkspaceMarkdownDocuments([doc])));
});
test('Path rename should use file path as range', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](./doc.md)`,
`[ref]: ./doc.md`,
));
const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.strictEqual(info!.placeholder, './doc.md');
assertRangeEqual(info!.range, new vscode.Range(0, 7, 0, 15));
});
test('Path rename\'s range should excludes fragment', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](./doc.md#some-header)`,
`[ref]: ./doc.md#some-header`,
));
const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.strictEqual(info!.placeholder, './doc.md');
assertRangeEqual(info!.range, new vscode.Range(0, 7, 0, 15));
});
test('Path rename should update file and all refs', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](./doc.md)`,
`[ref]: ./doc.md`,
));
const edit = await getRenameEdits(doc, new vscode.Position(0, 10), './sub/newDoc.md', new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
originalUri: uri,
newUri: workspacePath('sub', 'newDoc.md'),
}, {
uri: uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 15), './sub/newDoc.md'),
new vscode.TextEdit(new vscode.Range(1, 7, 1, 15), './sub/newDoc.md'),
]
});
});
test('Path rename using absolute file path should anchor to workspace root', async () => {
const uri = workspacePath('sub', 'doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](/sub/doc.md)`,
`[ref]: /sub/doc.md`,
));
const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/newSub/newDoc.md', new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
originalUri: uri,
newUri: workspacePath('newSub', 'newDoc.md'),
}, {
uri: uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 18), '/newSub/newDoc.md'),
new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/newSub/newDoc.md'),
]
});
});
test('Path rename should use un-encoded paths as placeholder', async () => {
const uri = workspacePath('sub', 'doc with spaces.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](/sub/doc%20with%20spaces.md)`,
));
const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.strictEqual(info!.placeholder, '/sub/doc with spaces.md');
});
test('Path rename should encode paths', async () => {
const uri = workspacePath('sub', 'doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](/sub/doc.md)`,
`[ref]: /sub/doc.md`,
));
const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/NEW sub/new DOC.md', new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
originalUri: uri,
newUri: workspacePath('NEW sub', 'new DOC.md'),
}, {
uri: uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 18), '/NEW%20sub/new%20DOC.md'),
new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/NEW%20sub/new%20DOC.md'),
]
});
});
test('Path rename should work with unknown files', async () => {
const uri1 = workspacePath('doc1.md');
const doc1 = new InMemoryDocument(uri1, joinLines(
`![img](/images/more/image.png)`,
``,
`[ref]: /images/more/image.png`,
));
const uri2 = workspacePath('sub', 'doc2.md');
const doc2 = new InMemoryDocument(uri2, joinLines(
`![img](/images/more/image.png)`,
));
const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), '/img/test/new.png', new InMemoryWorkspaceMarkdownDocuments([
doc1,
doc2
]));
assertEditsEqual(edit!,
// Should not have file edits since the files don't exist here
{
uri: uri1, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 29), '/img/test/new.png'),
new vscode.TextEdit(new vscode.Range(2, 7, 2, 29), '/img/test/new.png'),
]
},
{
uri: uri2, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 29), '/img/test/new.png'),
]
});
});
test('Path rename should use .md extension on extension-less link', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](/doc#header)`,
`[ref]: /doc#other`,
));
const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/new File', new InMemoryWorkspaceMarkdownDocuments([doc]));
assertEditsEqual(edit!, {
originalUri: uri,
newUri: workspacePath('new File.md'), // Rename on disk should use file extension
}, {
uri: uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 11), '/new%20File'), // Links should continue to use extension-less paths
new vscode.TextEdit(new vscode.Range(1, 7, 1, 11), '/new%20File'),
]
});
});
// TODO: fails on windows
test.skip('Path rename should use correctly resolved paths across files', async () => {
const uri1 = workspacePath('sub', 'doc.md');
const doc1 = new InMemoryDocument(uri1, joinLines(
`[text](./doc.md)`,
`[ref]: ./doc.md`,
));
const uri2 = workspacePath('doc2.md');
const doc2 = new InMemoryDocument(uri2, joinLines(
`[text](./sub/doc.md)`,
`[ref]: ./sub/doc.md`,
));
const uri3 = workspacePath('sub2', 'doc3.md');
const doc3 = new InMemoryDocument(uri3, joinLines(
`[text](../sub/doc.md)`,
`[ref]: ../sub/doc.md`,
));
const uri4 = workspacePath('sub2', 'doc4.md');
const doc4 = new InMemoryDocument(uri4, joinLines(
`[text](/sub/doc.md)`,
`[ref]: /sub/doc.md`,
));
const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), './new/new-doc.md', new InMemoryWorkspaceMarkdownDocuments([
doc1, doc2, doc3, doc4,
]));
assertEditsEqual(edit!, {
originalUri: uri1,
newUri: workspacePath('sub', 'new', 'new-doc.md'),
}, {
uri: uri1, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 15), './new/new-doc.md'),
new vscode.TextEdit(new vscode.Range(1, 7, 1, 15), './new/new-doc.md'),
]
}, {
uri: uri2, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 19), './sub/new/new-doc.md'),
new vscode.TextEdit(new vscode.Range(1, 7, 1, 19), './sub/new/new-doc.md'),
]
}, {
uri: uri3, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 20), '../sub/new/new-doc.md'),
new vscode.TextEdit(new vscode.Range(1, 7, 1, 20), '../sub/new/new-doc.md'),
]
}, {
uri: uri4, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 18), '/sub/new/new-doc.md'),
new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/sub/new/new-doc.md'),
]
});
});
test('Path rename should resolve on links without prefix', async () => {
const uri1 = workspacePath('sub', 'doc.md');
const doc1 = new InMemoryDocument(uri1, joinLines(
`![text](sub2/doc3.md)`,
));
const uri2 = workspacePath('doc2.md');
const doc2 = new InMemoryDocument(uri2, joinLines(
`![text](sub/sub2/doc3.md)`,
));
const uri3 = workspacePath('sub', 'sub2', 'doc3.md');
const doc3 = new InMemoryDocument(uri3, joinLines());
const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), 'sub2/cat.md', new InMemoryWorkspaceMarkdownDocuments([
doc1, doc2, doc3
]));
assertEditsEqual(edit!, {
originalUri: workspacePath('sub', 'sub2', 'doc3.md'),
newUri: workspacePath('sub', 'sub2', 'cat.md'),
}, {
uri: uri1, edits: [new vscode.TextEdit(new vscode.Range(0, 8, 0, 20), 'sub2/cat.md')]
}, {
uri: uri2, edits: [new vscode.TextEdit(new vscode.Range(0, 8, 0, 24), 'sub/sub2/cat.md')]
});
});
test('Rename on link should use header text as placeholder', async () => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`### a B c ###`,
`[text](#a-b-c)`,
));
const info = await prepareRename(doc, new vscode.Position(1, 10), new InMemoryWorkspaceMarkdownDocuments([doc]));
assert.strictEqual(info!.placeholder, 'a B c');
assertRangeEqual(info!.range, new vscode.Range(1, 8, 1, 13));
});
test('Rename on http uri should work', async () => {
const uri1 = workspacePath('doc.md');
const uri2 = workspacePath('doc2.md');
const doc = new InMemoryDocument(uri1, joinLines(
`[1](http://example.com)`,
`[2]: http://example.com`,
`<http://example.com>`,
));
const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "https://example.com/sub", new InMemoryWorkspaceMarkdownDocuments([
doc,
new InMemoryDocument(uri2, joinLines(
`[4](http://example.com)`,
))
]));
assertEditsEqual(edit!, {
uri: uri1, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 22), 'https://example.com/sub'),
new vscode.TextEdit(new vscode.Range(1, 5, 1, 23), 'https://example.com/sub'),
new vscode.TextEdit(new vscode.Range(2, 1, 2, 19), 'https://example.com/sub'),
]
}, {
uri: uri2, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 22), 'https://example.com/sub'),
]
});
});
});

View File

@@ -1,726 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as vscode from 'vscode';
import { MdSmartSelect } from '../languageFeatures/smartSelect';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { CURSOR, getCursorPositions, joinLines } from './util';
const testFileName = vscode.Uri.file('test.md');
suite('markdown.SmartSelect', () => {
test('Smart select single word', async () => {
const ranges = await getSelectionRangesForDocument(`Hel${CURSOR}lo`);
assertNestedLineNumbersEqual(ranges![0], [0, 0]);
});
test('Smart select multi-line paragraph', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`Many of the core components and extensions to ${CURSOR}VS Code live in their own repositories on GitHub. `,
`For example, the[node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter]`,
`(https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki).`
));
assertNestedLineNumbersEqual(ranges![0], [0, 2]);
});
test('Smart select paragraph', async () => {
const ranges = await getSelectionRangesForDocument(`Many of the core components and extensions to ${CURSOR}VS Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki).`);
assertNestedLineNumbersEqual(ranges![0], [0, 0]);
});
test('Smart select html block', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`<p align="center">`,
`${CURSOR}<img alt="VS Code in action" src="https://user-images.githubusercontent.com/1487073/58344409-70473b80-7e0a-11e9-8570-b2efc6f8fa44.png">`,
`</p>`));
assertNestedLineNumbersEqual(ranges![0], [0, 2]);
});
test('Smart select header on header line', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# Header${CURSOR}`,
`Hello`));
assertNestedLineNumbersEqual(ranges![0], [0, 1]);
});
test('Smart select single word w grandparent header on text line', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`## ParentHeader`,
`# Header`,
`${CURSOR}Hello`
));
assertNestedLineNumbersEqual(ranges![0], [2, 2], [1, 2]);
});
test('Smart select html block w parent header', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# Header`,
`${CURSOR}<p align="center">`,
`<img alt="VS Code in action" src="https://user-images.githubusercontent.com/1487073/58344409-70473b80-7e0a-11e9-8570-b2efc6f8fa44.png">`,
`</p>`));
assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 3], [0, 3]);
});
test('Smart select fenced code block', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`~~~`,
`a${CURSOR}`,
`~~~`));
assertNestedLineNumbersEqual(ranges![0], [0, 2]);
});
test('Smart select list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`- item 1`,
`- ${CURSOR}item 2`,
`- item 3`,
`- item 4`));
assertNestedLineNumbersEqual(ranges![0], [1, 1], [0, 3]);
});
test('Smart select list with fenced code block', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`- item 1`,
`- ~~~`,
` ${CURSOR}a`,
` ~~~`,
`- item 3`,
`- item 4`));
assertNestedLineNumbersEqual(ranges![0], [1, 3], [0, 5]);
});
test('Smart select multi cursor', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`- ${CURSOR}item 1`,
`- ~~~`,
` a`,
` ~~~`,
`- ${CURSOR}item 3`,
`- item 4`));
assertNestedLineNumbersEqual(ranges![0], [0, 0], [0, 5]);
assertNestedLineNumbersEqual(ranges![1], [4, 4], [0, 5]);
});
test('Smart select nested block quotes', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`> item 1`,
`> item 2`,
`>> ${CURSOR}item 3`,
`>> item 4`));
assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [0, 3]);
});
test('Smart select multi nested block quotes', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`> item 1`,
`>> item 2`,
`>>> ${CURSOR}item 3`,
`>>>> item 4`));
assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [1, 3], [0, 3]);
});
test('Smart select subheader content', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
`content 1`,
`## sub header 1`,
`${CURSOR}content 2`,
`# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [2, 3], [1, 3], [0, 3]);
});
test('Smart select subheader line', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
`content 1`,
`## sub header 1${CURSOR}`,
`content 2`,
`# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [2, 3], [1, 3], [0, 3]);
});
test('Smart select blank line', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
`content 1`,
`${CURSOR} `,
`content 2`,
`# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [1, 3], [0, 3]);
});
test('Smart select line between paragraphs', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`paragraph 1`,
`${CURSOR}`,
`paragraph 2`));
assertNestedLineNumbersEqual(ranges![0], [0, 2]);
});
test('Smart select empty document', async () => {
const ranges = await getSelectionRangesForDocument(``, [new vscode.Position(0, 0)]);
assert.strictEqual(ranges!.length, 0);
});
test('Smart select fenced code block then list then subheader content then subheader then header content then header', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
/* 00 */ `# main header 1`,
/* 01 */ `content 1`,
/* 02 */ `## sub header 1`,
/* 03 */ `- item 1`,
/* 04 */ `- ~~~`,
/* 05 */ ` ${CURSOR}a`,
/* 06 */ ` ~~~`,
/* 07 */ `- item 3`,
/* 08 */ `- item 4`,
/* 09 */ ``,
/* 10 */ `more content`,
/* 11 */ `# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [4, 6], [3, 8], [3, 10], [2, 10], [1, 10], [0, 10]);
});
test('Smart select list with one element without selecting child subheader', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
/* 00 */ `# main header 1`,
/* 01 */ ``,
/* 02 */ `- list ${CURSOR}`,
/* 03 */ ``,
/* 04 */ `## sub header`,
/* 05 */ ``,
/* 06 */ `content 2`,
/* 07 */ `# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [2, 2], [1, 3], [1, 6], [0, 6]);
});
test('Smart select content under header then subheaders and their content', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main ${CURSOR}header 1`,
``,
`- list`,
`paragraph`,
`## sub header`,
``,
`content 2`,
`# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [0, 3], [0, 6]);
});
test('Smart select last blockquote element under header then subheaders and their content', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> ${CURSOR}block`,
``,
`paragraph`,
`## sub header`,
``,
`content 2`,
`# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [5, 5], [4, 5], [2, 5], [1, 7], [1, 10], [0, 10]);
});
test('Smart select content of subheader then subheader then content of main header then main header', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> block`,
``,
`paragraph`,
`## sub header`,
``,
``,
`${CURSOR}`,
``,
`### main header 2`,
`- content 2`,
`- content 2`,
`- content 2`,
`content 2`));
assertNestedLineNumbersEqual(ranges![0], [11, 11], [9, 12], [9, 17], [8, 17], [1, 17], [0, 17]);
});
test('Smart select last line content of subheader then subheader then content of main header then main header', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> block`,
``,
`paragraph`,
`## sub header`,
``,
``,
``,
``,
`### main header 2`,
`- content 2`,
`- content 2`,
`- content 2`,
`- ${CURSOR}content 2`));
assertNestedLineNumbersEqual(ranges![0], [17, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]);
});
test('Smart select last line content after content of subheader then subheader then content of main header then main header', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> block`,
``,
`paragraph`,
`## sub header`,
``,
``,
``,
``,
`### main header 2`,
`- content 2`,
`- content 2`,
`- content 2`,
`- content 2${CURSOR}`));
assertNestedLineNumbersEqual(ranges![0], [17, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]);
});
test('Smart select fenced code block then list then rest of content', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> block`,
``,
`- paragraph`,
`- ~~~`,
` my`,
` ${CURSOR}code`,
` goes here`,
` ~~~`,
`- content`,
`- content 2`,
`- content 2`,
`- content 2`,
`- content 2`));
assertNestedLineNumbersEqual(ranges![0], [9, 11], [8, 12], [8, 12], [7, 17], [1, 17], [0, 17]);
});
test('Smart select fenced code block then list then rest of content on fenced line', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`> block`,
`> block`,
`>> block`,
`>> block`,
``,
`- paragraph`,
`- ~~~${CURSOR}`,
` my`,
` code`,
` goes here`,
` ~~~`,
`- content`,
`- content 2`,
`- content 2`,
`- content 2`,
`- content 2`));
assertNestedLineNumbersEqual(ranges![0], [8, 12], [7, 17], [1, 17], [0, 17]);
});
test('Smart select without multiple ranges', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
``,
`- ${CURSOR}paragraph`,
`- content`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [1, 4], [0, 4]);
});
test('Smart select on second level of a list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 0`,
` * level 1`,
` * level 1`,
` * level 2`,
` * level 1`,
` * level ${CURSOR}1`,
`* level 0`));
assertNestedLineNumbersEqual(ranges![0], [5, 5], [1, 5], [0, 5], [0, 6]);
});
test('Smart select on third level of a list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 0`,
` * level 1`,
` * level 1`,
` * level ${CURSOR}2`,
` * level 2`,
` * level 1`,
` * level 1`,
`* level 0`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [2, 4], [1, 6], [0, 6], [0, 7]);
});
test('Smart select level 2 then level 1', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 1`,
` * level ${CURSOR}2`,
` * level 2`,
`* level 1`));
assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 2], [0, 2], [0, 3]);
});
test('Smart select last list item', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`- level 1`,
`- level 2`,
`- level 2`,
`- level ${CURSOR}1`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [0, 3]);
});
test('Smart select without multiple ranges', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
``,
`- ${CURSOR}paragraph`,
`- content`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [1, 4], [0, 4]);
});
test('Smart select on second level of a list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 0`,
` * level 1`,
` * level 1`,
` * level 2`,
` * level 1`,
` * level ${CURSOR}1`,
`* level 0`));
assertNestedLineNumbersEqual(ranges![0], [5, 5], [1, 5], [0, 5], [0, 6]);
});
test('Smart select on third level of a list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 0`,
` * level 1`,
` * level 1`,
` * level ${CURSOR}2`,
` * level 2`,
` * level 1`,
` * level 1`,
`* level 0`));
assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [2, 4], [1, 6], [0, 6], [0, 7]);
});
test('Smart select level 2 then level 1', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`* level 1`,
` * level ${CURSOR}2`,
` * level 2`,
`* level 1`));
assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 2], [0, 2], [0, 3]);
});
test('Smart select bold', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`stuff here **new${CURSOR}item** and here`
));
assertNestedRangesEqual(ranges![0], [0, 13, 0, 30], [0, 11, 0, 32], [0, 0, 0, 41]);
});
test('Smart select link', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`stuff here [text](https${CURSOR}://google.com) and here`
));
assertNestedRangesEqual(ranges![0], [0, 18, 0, 46], [0, 17, 0, 47], [0, 11, 0, 47], [0, 0, 0, 56]);
});
test('Smart select brackets', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`stuff here [te${CURSOR}xt](https://google.com) and here`
));
assertNestedRangesEqual(ranges![0], [0, 12, 0, 26], [0, 11, 0, 27], [0, 11, 0, 47], [0, 0, 0, 56]);
});
test('Smart select brackets under header in list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`- list`,
`paragraph`,
`## sub header`,
`- list`,
`- stuff here [te${CURSOR}xt](https://google.com) and here`,
`- list`
));
assertNestedRangesEqual(ranges![0], [6, 14, 6, 28], [6, 13, 6, 29], [6, 13, 6, 49], [6, 0, 6, 58], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]);
});
test('Smart select link under header in list', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`- list`,
`paragraph`,
`## sub header`,
`- list`,
`- stuff here [text](${CURSOR}https://google.com) and here`,
`- list`
));
assertNestedRangesEqual(ranges![0], [6, 20, 6, 48], [6, 19, 6, 49], [6, 13, 6, 49], [6, 0, 6, 58], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]);
});
test('Smart select bold within list where multiple bold elements exists', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`- list`,
`paragraph`,
`## sub header`,
`- list`,
`- stuff here [text] **${CURSOR}items in here** and **here**`,
`- list`
));
assertNestedRangesEqual(ranges![0], [6, 22, 6, 45], [6, 20, 6, 47], [6, 0, 6, 60], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]);
});
test('Smart select link in paragraph with multiple links', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`This[extension](https://marketplace.visualstudio.com/items?itemName=meganrogge.template-string-converter) addresses this [requ${CURSOR}est](https://github.com/microsoft/vscode/issues/56704) to convert Javascript/Typescript quotes to backticks when has been entered within a string.`
));
assertNestedRangesEqual(ranges![0], [0, 123, 0, 140], [0, 122, 0, 141], [0, 122, 0, 191], [0, 0, 0, 283]);
});
test('Smart select bold link', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`**[extens${CURSOR}ion](https://google.com)**`
));
assertNestedRangesEqual(ranges![0], [0, 3, 0, 22], [0, 2, 0, 23], [0, 2, 0, 43], [0, 2, 0, 43], [0, 0, 0, 45], [0, 0, 0, 45]);
});
test('Smart select inline code block', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`[\`code ${CURSOR} link\`]`
));
assertNestedRangesEqual(ranges![0], [0, 2, 0, 22], [0, 1, 0, 23], [0, 0, 0, 24]);
});
test('Smart select link with inline code block text', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`[\`code ${CURSOR} link\`](http://example.com)`
));
assertNestedRangesEqual(ranges![0], [0, 2, 0, 22], [0, 1, 0, 23], [0, 1, 0, 23], [0, 0, 0, 24], [0, 0, 0, 44], [0, 0, 0, 44]);
});
test('Smart select italic', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`*some nice ${CURSOR}text*`
));
assertNestedRangesEqual(ranges![0], [0, 1, 0, 25], [0, 0, 0, 26], [0, 0, 0, 26]);
});
test('Smart select italic link', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`*[extens${CURSOR}ion](https://google.com)*`
));
assertNestedRangesEqual(ranges![0], [0, 2, 0, 21], [0, 1, 0, 22], [0, 1, 0, 42], [0, 1, 0, 42], [0, 0, 0, 43], [0, 0, 0, 43]);
});
test('Smart select italic on end', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`*word1 word2 word3${CURSOR}*`
));
assertNestedRangesEqual(ranges![0], [0, 1, 0, 28], [0, 0, 0, 29], [0, 0, 0, 29]);
});
test('Smart select italic then bold', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`outer text **bold words *italic ${CURSOR} words* bold words** outer text`
));
assertNestedRangesEqual(ranges![0], [0, 25, 0, 48], [0, 24, 0, 49], [0, 13, 0, 60], [0, 11, 0, 62], [0, 0, 0, 73]);
});
test('Smart select bold then italic', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`outer text *italic words **bold ${CURSOR} words** italic words* outer text`
));
assertNestedRangesEqual(ranges![0], [0, 27, 0, 48], [0, 25, 0, 50], [0, 12, 0, 63], [0, 11, 0, 64], [0, 0, 0, 75]);
});
test('Third level header from release notes', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`---`,
`Order: 60`,
`TOCTitle: October 2020`,
`PageTitle: Visual Studio Code October 2020`,
`MetaDescription: Learn what is new in the Visual Studio Code October 2020 Release (1.51)`,
`MetaSocialImage: 1_51/release-highlights.png`,
`Date: 2020-11-6`,
`DownloadVersion: 1.51.1`,
`---`,
`# October 2020 (version 1.51)`,
``,
`**Update 1.51.1**: The update addresses these [issues](https://github.com/microsoft/vscode/issues?q=is%3Aissue+milestone%3A%22October+2020+Recovery%22+is%3Aclosed+).`,
``,
`<!-- DOWNLOAD_LINKS_PLACEHOLDER -->`,
``,
`---`,
``,
`Welcome to the October 2020 release of Visual Studio Code. As announced in the [October iteration plan](https://github.com/microsoft/vscode/issues/108473), we focused on housekeeping GitHub issues and pull requests as documented in our issue grooming guide.`,
``,
`We also worked with our partners at GitHub on GitHub Codespaces, which ended up being more involved than originally anticipated. To that end, we'll continue working on housekeeping for part of the November iteration.`,
``,
`During this housekeeping milestone, we also addressed several feature requests and community [pull requests](#thank-you). Read on to learn about new features and settings.`,
``,
`## Workbench`,
``,
`### More prominent pinned tabs`,
``,
`${CURSOR}Pinned tabs will now always show their pin icon, even while inactive, to make them easier to identify. If an editor is both pinned and contains unsaved changes, the icon reflects both states.`,
``,
`![Inactive pinned tabs showing pin icons](images/1_51/pinned-tabs.png)`
)
);
assertNestedRangesEqual(ranges![0], [27, 0, 27, 201], [26, 0, 29, 70], [25, 0, 29, 70], [24, 0, 29, 70], [23, 0, 29, 70], [10, 0, 29, 70], [9, 0, 29, 70]);
});
});
function assertNestedLineNumbersEqual(range: vscode.SelectionRange, ...expectedRanges: [number, number][]) {
const lineage = getLineage(range);
assert.strictEqual(lineage.length, expectedRanges.length, `expected depth: ${expectedRanges.length}, but was ${lineage.length} ${getValues(lineage)}`);
for (let i = 0; i < lineage.length; i++) {
assertLineNumbersEqual(lineage[i], expectedRanges[i][0], expectedRanges[i][1], `parent at a depth of ${i}`);
}
}
function assertNestedRangesEqual(range: vscode.SelectionRange, ...expectedRanges: [number, number, number, number][]) {
const lineage = getLineage(range);
assert.strictEqual(lineage.length, expectedRanges.length, `expected depth: ${expectedRanges.length}, but was ${lineage.length} ${getValues(lineage)}`);
for (let i = 0; i < lineage.length; i++) {
assertLineNumbersEqual(lineage[i], expectedRanges[i][0], expectedRanges[i][2], `parent at a depth of ${i}`);
assert(lineage[i].range.start.character === expectedRanges[i][1], `parent at a depth of ${i} on start char`);
assert(lineage[i].range.end.character === expectedRanges[i][3], `parent at a depth of ${i} on end char`);
}
}
function getLineage(range: vscode.SelectionRange): vscode.SelectionRange[] {
const result: vscode.SelectionRange[] = [];
let currentRange: vscode.SelectionRange | undefined = range;
while (currentRange) {
result.push(currentRange);
currentRange = currentRange.parent;
}
return result;
}
function getValues(ranges: vscode.SelectionRange[]): string[] {
return ranges.map(range => {
return range.range.start.line + ' ' + range.range.start.character + ' ' + range.range.end.line + ' ' + range.range.end.character;
});
}
function assertLineNumbersEqual(selectionRange: vscode.SelectionRange, startLine: number, endLine: number, message: string) {
assert.strictEqual(selectionRange.range.start.line, startLine, `failed on start line ${message}`);
assert.strictEqual(selectionRange.range.end.line, endLine, `failed on end line ${message}`);
}
function getSelectionRangesForDocument(contents: string, pos?: vscode.Position[]): Promise<vscode.SelectionRange[] | undefined> {
const doc = new InMemoryDocument(testFileName, contents);
const provider = new MdSmartSelect(createNewMarkdownEngine());
const positions = pos ? pos : getCursorPositions(contents, doc);
return provider.provideSelectionRanges(doc, positions, new vscode.CancellationTokenSource().token);
}

View File

@@ -1,130 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { TableOfContents } from '../tableOfContents';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from '../util/inMemoryDocument';
const testFileName = vscode.Uri.file('test.md');
suite('markdown.TableOfContentsProvider', () => {
test('Lookup should not return anything for empty document', async () => {
const doc = new InMemoryDocument(testFileName, '');
const provider = await TableOfContents.create(createNewMarkdownEngine(), doc);
assert.strictEqual(provider.lookup(''), undefined);
assert.strictEqual(provider.lookup('foo'), undefined);
});
test('Lookup should not return anything for document with no headers', async () => {
const doc = new InMemoryDocument(testFileName, 'a *b*\nc');
const provider = await TableOfContents.create(createNewMarkdownEngine(), doc);
assert.strictEqual(provider.lookup(''), undefined);
assert.strictEqual(provider.lookup('foo'), undefined);
assert.strictEqual(provider.lookup('a'), undefined);
assert.strictEqual(provider.lookup('b'), undefined);
});
test('Lookup should return basic #header', async () => {
const doc = new InMemoryDocument(testFileName, `# a\nx\n# c`);
const provider = await TableOfContents.create(createNewMarkdownEngine(), doc);
{
const entry = provider.lookup('a');
assert.ok(entry);
assert.strictEqual(entry!.line, 0);
}
{
assert.strictEqual(provider.lookup('x'), undefined);
}
{
const entry = provider.lookup('c');
assert.ok(entry);
assert.strictEqual(entry!.line, 2);
}
});
test('Lookups should be case in-sensitive', async () => {
const doc = new InMemoryDocument(testFileName, `# fOo\n`);
const provider = await TableOfContents.create(createNewMarkdownEngine(), doc);
assert.strictEqual((provider.lookup('fOo'))!.line, 0);
assert.strictEqual((provider.lookup('foo'))!.line, 0);
assert.strictEqual((provider.lookup('FOO'))!.line, 0);
});
test('Lookups should ignore leading and trailing white-space, and collapse internal whitespace', async () => {
const doc = new InMemoryDocument(testFileName, `# f o o \n`);
const provider = await TableOfContents.create(createNewMarkdownEngine(), doc);
assert.strictEqual((provider.lookup('f o o'))!.line, 0);
assert.strictEqual((provider.lookup(' f o o'))!.line, 0);
assert.strictEqual((provider.lookup(' f o o '))!.line, 0);
assert.strictEqual((provider.lookup('f o o'))!.line, 0);
assert.strictEqual((provider.lookup('f o o'))!.line, 0);
assert.strictEqual(provider.lookup('f'), undefined);
assert.strictEqual(provider.lookup('foo'), undefined);
assert.strictEqual(provider.lookup('fo o'), undefined);
});
test('should handle special characters #44779', async () => {
const doc = new InMemoryDocument(testFileName, `# Indentação\n`);
const provider = await TableOfContents.create(createNewMarkdownEngine(), doc);
assert.strictEqual((provider.lookup('indentação'))!.line, 0);
});
test('should handle special characters 2, #48482', async () => {
const doc = new InMemoryDocument(testFileName, `# Инструкция - Делай Раз, Делай Два\n`);
const provider = await TableOfContents.create(createNewMarkdownEngine(), doc);
assert.strictEqual((provider.lookup('инструкция---делай-раз-делай-два'))!.line, 0);
});
test('should handle special characters 3, #37079', async () => {
const doc = new InMemoryDocument(testFileName, `## Header 2
### Header 3
## Заголовок 2
### Заголовок 3
### Заголовок Header 3
## Заголовок`);
const provider = await TableOfContents.create(createNewMarkdownEngine(), doc);
assert.strictEqual((provider.lookup('header-2'))!.line, 0);
assert.strictEqual((provider.lookup('header-3'))!.line, 1);
assert.strictEqual((provider.lookup('Заголовок-2'))!.line, 2);
assert.strictEqual((provider.lookup('Заголовок-3'))!.line, 3);
assert.strictEqual((provider.lookup('Заголовок-header-3'))!.line, 4);
assert.strictEqual((provider.lookup('Заголовок'))!.line, 5);
});
test('Lookup should support suffixes for repeated headers', async () => {
const doc = new InMemoryDocument(testFileName, `# a\n# a\n## a`);
const provider = await TableOfContents.create(createNewMarkdownEngine(), doc);
{
const entry = provider.lookup('a');
assert.ok(entry);
assert.strictEqual(entry!.line, 0);
}
{
const entry = provider.lookup('a-1');
assert.ok(entry);
assert.strictEqual(entry!.line, 1);
}
{
const entry = provider.lookup('a-2');
assert.ok(entry);
assert.strictEqual(entry!.line, 2);
}
});
});

View File

@@ -5,33 +5,11 @@
import * as assert from 'assert';
import * as os from 'os';
import * as vscode from 'vscode';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { DisposableStore } from '../util/dispose';
export const joinLines = (...args: string[]) =>
args.join(os.platform() === 'win32' ? '\r\n' : '\n');
export const noopToken = new class implements vscode.CancellationToken {
_onCancellationRequestedEmitter = new vscode.EventEmitter<void>();
onCancellationRequested = this._onCancellationRequestedEmitter.event;
get isCancellationRequested() { return false; }
};
export const CURSOR = '$$CURSOR$$';
export function getCursorPositions(contents: string, doc: InMemoryDocument): vscode.Position[] {
let positions: vscode.Position[] = [];
let index = 0;
let wordLength = 0;
while (index !== -1) {
index = contents.indexOf(CURSOR, index + wordLength);
if (index !== -1) {
positions.push(doc.positionAt(index));
}
wordLength = CURSOR.length;
}
return positions;
}
export function workspacePath(...segments: string[]): vscode.Uri {
return vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, ...segments);
@@ -43,3 +21,14 @@ export function assertRangeEqual(expected: vscode.Range, actual: vscode.Range, m
assert.strictEqual(expected.end.line, actual.end.line, message);
assert.strictEqual(expected.end.character, actual.end.character, message);
}
export function withStore<R>(fn: (this: Mocha.Context, store: DisposableStore) => Promise<R>) {
return async function (this: Mocha.Context): Promise<R> {
const store = new DisposableStore();
try {
return await fn.call(this, store);
} finally {
store.dispose();
}
};
}

View File

@@ -1,103 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbolProvider';
import { MdWorkspaceSymbolProvider } from '../languageFeatures/workspaceSymbolProvider';
import { SkinnyTextDocument } from '../workspaceContents';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
const symbolProvider = new MdDocumentSymbolProvider(createNewMarkdownEngine());
suite('markdown.WorkspaceSymbolProvider', () => {
test('Should not return anything for empty workspace', async () => {
const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocuments([]));
assert.deepStrictEqual(await provider.provideWorkspaceSymbols(''), []);
});
test('Should return symbols from workspace with one markdown file', async () => {
const testFileName = vscode.Uri.file('test.md');
const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocuments([
new InMemoryDocument(testFileName, `# header1\nabc\n## header2`)
]));
const symbols = await provider.provideWorkspaceSymbols('');
assert.strictEqual(symbols.length, 2);
assert.strictEqual(symbols[0].name, '# header1');
assert.strictEqual(symbols[1].name, '## header2');
});
test('Should return all content basic workspace', async () => {
const fileNameCount = 10;
const files: SkinnyTextDocument[] = [];
for (let i = 0; i < fileNameCount; ++i) {
const testFileName = vscode.Uri.file(`test${i}.md`);
files.push(new InMemoryDocument(testFileName, `# common\nabc\n## header${i}`));
}
const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocuments(files));
const symbols = await provider.provideWorkspaceSymbols('');
assert.strictEqual(symbols.length, fileNameCount * 2);
});
test('Should update results when markdown file changes symbols', async () => {
const testFileName = vscode.Uri.file('test.md');
const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocuments([
new InMemoryDocument(testFileName, `# header1`, 1 /* version */)
]);
const provider = new MdWorkspaceSymbolProvider(symbolProvider, workspaceFileProvider);
assert.strictEqual((await provider.provideWorkspaceSymbols('')).length, 1);
// Update file
workspaceFileProvider.updateDocument(new InMemoryDocument(testFileName, `# new header\nabc\n## header2`, 2 /* version */));
const newSymbols = await provider.provideWorkspaceSymbols('');
assert.strictEqual(newSymbols.length, 2);
assert.strictEqual(newSymbols[0].name, '# new header');
assert.strictEqual(newSymbols[1].name, '## header2');
});
test('Should remove results when file is deleted', async () => {
const testFileName = vscode.Uri.file('test.md');
const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocuments([
new InMemoryDocument(testFileName, `# header1`)
]);
const provider = new MdWorkspaceSymbolProvider(symbolProvider, workspaceFileProvider);
assert.strictEqual((await provider.provideWorkspaceSymbols('')).length, 1);
// delete file
workspaceFileProvider.deleteDocument(testFileName);
const newSymbols = await provider.provideWorkspaceSymbols('');
assert.strictEqual(newSymbols.length, 0);
});
test('Should update results when markdown file is created', async () => {
const testFileName = vscode.Uri.file('test.md');
const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocuments([
new InMemoryDocument(testFileName, `# header1`)
]);
const provider = new MdWorkspaceSymbolProvider(symbolProvider, workspaceFileProvider);
assert.strictEqual((await provider.provideWorkspaceSymbols('')).length, 1);
// Creat file
workspaceFileProvider.createDocument(new InMemoryDocument(vscode.Uri.file('test2.md'), `# new header\nabc\n## header2`));
const newSymbols = await provider.provideWorkspaceSymbols('');
assert.strictEqual(newSymbols.length, 3);
});
});