mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 21:55:38 +00:00
Add support for renaming files in markdown using F2
This lets you trigger F2 on a file path in a markdown link to both rename the file and also update all references to it
This commit is contained in:
parent
9fbd962973
commit
0ac39e800d
|
@ -71,7 +71,7 @@ function registerMarkdownLanguageFeatures(
|
|||
vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(engine)),
|
||||
vscode.languages.registerWorkspaceSymbolProvider(new MdWorkspaceSymbolProvider(symbolProvider, workspaceContents)),
|
||||
vscode.languages.registerReferenceProvider(selector, referencesProvider),
|
||||
vscode.languages.registerRenameProvider(selector, new MdRenameProvider(referencesProvider, githubSlugifier)),
|
||||
vscode.languages.registerRenameProvider(selector, new MdRenameProvider(referencesProvider, workspaceContents, githubSlugifier)),
|
||||
MdPathCompletionProvider.register(selector, engine, linkProvider),
|
||||
registerDropIntoEditor(selector),
|
||||
registerFindFileReferences(commandManager, referencesProvider),
|
||||
|
|
|
@ -176,22 +176,14 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
return references;
|
||||
}
|
||||
|
||||
let targetDoc = await this.workspaceContents.getMarkdownDocument(sourceLink.href.path);
|
||||
if (!targetDoc) {
|
||||
// We don't think the file exists. If it doesn't already have an extension, try tacking on a `.md` and using that instead
|
||||
if (uri.Utils.extname(sourceLink.href.path) === '') {
|
||||
const dotMdResource = sourceLink.href.path.with({ path: sourceLink.href.path.path + '.md' });
|
||||
targetDoc = await this.workspaceContents.getMarkdownDocument(dotMdResource);
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetDoc || token.isCancellationRequested) {
|
||||
const targetDoc = await tryFindMdDocumentForLink(sourceLink.href, this.workspaceContents);
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const references: MdReference[] = [];
|
||||
|
||||
if (sourceLink.href.fragment && sourceLink.source.fragmentRange?.contains(triggerPosition)) {
|
||||
if (targetDoc && sourceLink.href.fragment && sourceLink.source.fragmentRange?.contains(triggerPosition)) {
|
||||
const toc = await TableOfContents.create(this.engine, targetDoc);
|
||||
const entry = toc.lookup(sourceLink.href.fragment);
|
||||
if (entry) {
|
||||
|
@ -222,7 +214,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
}
|
||||
}
|
||||
} else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments
|
||||
references.push(...this.findAllLinksToFile(targetDoc.uri, allLinksInWorkspace, sourceLink));
|
||||
references.push(...this.findAllLinksToFile(targetDoc?.uri ?? sourceLink.href.path, allLinksInWorkspace, sourceLink));
|
||||
}
|
||||
|
||||
return references;
|
||||
|
@ -297,3 +289,19 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
|
|||
: link.source.hrefRange;
|
||||
}
|
||||
}
|
||||
|
||||
export async function tryFindMdDocumentForLink(href: InternalHref, workspaceContents: MdWorkspaceContents): Promise<SkinnyTextDocument | undefined> {
|
||||
const targetDoc = await workspaceContents.getMarkdownDocument(href.path);
|
||||
if (targetDoc) {
|
||||
return targetDoc;
|
||||
}
|
||||
|
||||
// We don't think the file exists. If it doesn't already have an extension, try tacking on a `.md` and using that instead
|
||||
if (uri.Utils.extname(href.path) === '') {
|
||||
const dotMdResource = href.path.with({ path: href.path.path + '.md' });
|
||||
return workspaceContents.getMarkdownDocument(dotMdResource);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,12 +6,35 @@ import * as vscode from 'vscode';
|
|||
import * as nls from 'vscode-nls';
|
||||
import { Slugifier } from '../slugify';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { SkinnyTextDocument } from '../workspaceContents';
|
||||
import { MdHeaderReference, MdReference, MdReferencesProvider } from './references';
|
||||
import { resolveDocumentLink } from '../util/openDocumentLink';
|
||||
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
|
||||
import { InternalHref } from './documentLinkProvider';
|
||||
import { MdHeaderReference, MdLinkReference, MdReference, MdReferencesProvider, tryFindMdDocumentForLink } from './references';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export interface MdReferencesResponse {
|
||||
references: MdReference[];
|
||||
triggerRef: MdReference;
|
||||
}
|
||||
|
||||
interface MdFileRenameEdit {
|
||||
readonly from: string;
|
||||
readonly to: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type with additional metadata about the edits for testing
|
||||
*
|
||||
* This is needed since `vscode.WorkspaceEdit` does not expose info on file renames.
|
||||
*/
|
||||
export interface MdWorkspaceEdit {
|
||||
readonly edit: vscode.WorkspaceEdit;
|
||||
|
||||
readonly fileRenames?: ReadonlyArray<MdFileRenameEdit>;
|
||||
}
|
||||
|
||||
export class MdRenameProvider extends Disposable implements vscode.RenameProvider {
|
||||
|
||||
private cachedRefs?: {
|
||||
|
@ -22,8 +45,11 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
|||
readonly references: MdReference[];
|
||||
} | undefined;
|
||||
|
||||
private readonly renameNotSupportedText = localize('invalidRenameLocation', "Rename not supported at location");
|
||||
|
||||
public constructor(
|
||||
private readonly referencesProvider: MdReferencesProvider,
|
||||
private readonly workspaceContents: MdWorkspaceContents,
|
||||
private readonly slugifier: Slugifier,
|
||||
) {
|
||||
super();
|
||||
|
@ -36,7 +62,7 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
|||
}
|
||||
|
||||
if (!allRefsInfo || !allRefsInfo.references.length) {
|
||||
throw new Error(localize('invalidRenameLocation', "Rename not supported at location"));
|
||||
throw new Error(this.renameNotSupportedText);
|
||||
}
|
||||
|
||||
const triggerRef = allRefsInfo.triggerRef;
|
||||
|
@ -56,8 +82,9 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
|||
return { range: triggerRef.link.source.hrefRange, placeholder: document.getText(triggerRef.link.source.hrefRange) };
|
||||
}
|
||||
|
||||
// See if we are renaming the fragment or the path
|
||||
const { fragmentRange } = triggerRef.link.source;
|
||||
if (fragmentRange) {
|
||||
if (fragmentRange?.contains(position)) {
|
||||
const declaration = this.findHeaderDeclaration(allRefsInfo.references);
|
||||
if (declaration) {
|
||||
return { range: fragmentRange, placeholder: declaration.headerText };
|
||||
|
@ -65,16 +92,31 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
|||
return { range: fragmentRange, placeholder: document.getText(fragmentRange) };
|
||||
}
|
||||
|
||||
throw new Error(localize('renameNoFiles', "Renaming files is currently not supported"));
|
||||
const range = this.getFilePathRange(triggerRef);
|
||||
if (!range) {
|
||||
throw new Error(this.renameNotSupportedText);
|
||||
}
|
||||
return { range, placeholder: document.getText(range) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getFilePathRange(ref: MdLinkReference): vscode.Range {
|
||||
if (ref.link.source.fragmentRange) {
|
||||
return ref.link.source.hrefRange.with(undefined, ref.link.source.fragmentRange.start.translate(0, -1));
|
||||
}
|
||||
return ref.link.source.hrefRange;
|
||||
}
|
||||
|
||||
private findHeaderDeclaration(references: readonly MdReference[]): MdHeaderReference | undefined {
|
||||
return references.find(ref => ref.isDefinition && ref.kind === 'header') as MdHeaderReference | undefined;
|
||||
}
|
||||
|
||||
public async provideRenameEdits(document: SkinnyTextDocument, position: vscode.Position, newName: string, token: vscode.CancellationToken): Promise<vscode.WorkspaceEdit | undefined> {
|
||||
return (await this.provideRenameEditsImpl(document, position, newName, token))?.edit;
|
||||
}
|
||||
|
||||
public async provideRenameEditsImpl(document: SkinnyTextDocument, position: vscode.Position, newName: string, token: vscode.CancellationToken): Promise<MdWorkspaceEdit | undefined> {
|
||||
const allRefsInfo = await this.getAllReferences(document, position, token);
|
||||
if (token.isCancellationRequested || !allRefsInfo || !allRefsInfo.references.length) {
|
||||
return undefined;
|
||||
|
@ -82,9 +124,44 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
|||
|
||||
const triggerRef = allRefsInfo.triggerRef;
|
||||
|
||||
const isRefRename = triggerRef.kind === 'link' && (
|
||||
if (triggerRef.kind === 'link' && (
|
||||
(triggerRef.link.kind === 'definition' && triggerRef.link.ref.range.contains(position)) || triggerRef.link.href.kind === 'reference'
|
||||
);
|
||||
)) {
|
||||
return this.renameReferenceLinks(allRefsInfo, newName);
|
||||
} else if (triggerRef.kind === 'link' && triggerRef.link.href.kind === 'external') {
|
||||
return this.renameExternalLink(allRefsInfo, newName);
|
||||
} else if (triggerRef.kind === 'header' || (triggerRef.kind === 'link' && triggerRef.link.source.fragmentRange?.contains(position) && (triggerRef.link.kind === 'definition' || triggerRef.link.kind === 'link' && triggerRef.link.href.kind === 'internal'))) {
|
||||
return this.renameFragment(allRefsInfo, newName);
|
||||
} else if (triggerRef.kind === 'link' && !triggerRef.link.source.fragmentRange?.contains(position) && triggerRef.link.kind === 'link' && triggerRef.link.href.kind === 'internal') {
|
||||
return this.renameFilePath(triggerRef.link.href, allRefsInfo, newName);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async renameFilePath(triggerHref: InternalHref, allRefsInfo: MdReferencesResponse, newName: string): Promise<MdWorkspaceEdit> {
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
const fileRenames: MdFileRenameEdit[] = [];
|
||||
|
||||
const targetDoc = await tryFindMdDocumentForLink(triggerHref, this.workspaceContents);
|
||||
const targetUri = targetDoc?.uri ?? triggerHref.path;
|
||||
const newFilePath = resolveDocumentLink(newName, triggerHref.path);
|
||||
|
||||
// First rename the file
|
||||
fileRenames.push({ from: targetUri.toString(), to: newFilePath.toString() });
|
||||
edit.renameFile(targetUri, newFilePath);
|
||||
|
||||
// Then update all refs to it
|
||||
for (const ref of allRefsInfo.references) {
|
||||
if (ref.kind === 'link') {
|
||||
edit.replace(ref.link.source.resource, this.getFilePathRange(ref), encodeURI(newName));
|
||||
}
|
||||
}
|
||||
|
||||
return { edit, fileRenames };
|
||||
}
|
||||
|
||||
private renameFragment(allRefsInfo: MdReferencesResponse, newName: string): MdWorkspaceEdit {
|
||||
const slug = this.slugifier.fromHeading(newName).value;
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
|
@ -95,22 +172,38 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide
|
|||
break;
|
||||
|
||||
case 'link':
|
||||
if (ref.link.kind === 'definition') {
|
||||
// We may be renaming either the reference or the definition itself
|
||||
if (isRefRename) {
|
||||
edit.replace(ref.link.source.resource, ref.link.ref.range, newName);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
edit.replace(ref.link.source.resource, ref.link.source.fragmentRange ?? ref.location.range, isRefRename && !ref.link.source.fragmentRange || ref.link.href.kind === 'external' ? newName : slug);
|
||||
edit.replace(ref.link.source.resource, ref.link.source.fragmentRange ?? ref.location.range, !ref.link.source.fragmentRange || ref.link.href.kind === 'external' ? newName : slug);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return edit;
|
||||
return { edit };
|
||||
}
|
||||
|
||||
private async getAllReferences(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<{ references: MdReference[]; triggerRef: MdReference } | undefined> {
|
||||
private renameExternalLink(allRefsInfo: MdReferencesResponse, newName: string): MdWorkspaceEdit {
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
for (const ref of allRefsInfo.references) {
|
||||
if (ref.kind === 'link') {
|
||||
edit.replace(ref.link.source.resource, ref.location.range, newName);
|
||||
}
|
||||
}
|
||||
return { edit };
|
||||
}
|
||||
|
||||
private renameReferenceLinks(allRefsInfo: MdReferencesResponse, newName: string): MdWorkspaceEdit {
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
for (const ref of allRefsInfo.references) {
|
||||
if (ref.kind === 'link') {
|
||||
if (ref.link.kind === 'definition') {
|
||||
edit.replace(ref.link.source.resource, ref.link.ref.range, newName);
|
||||
} else {
|
||||
edit.replace(ref.link.source.resource, ref.link.source.fragmentRange ?? ref.location.range, newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { edit };
|
||||
}
|
||||
|
||||
private async getAllReferences(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReferencesResponse | undefined> {
|
||||
const version = document.version;
|
||||
|
||||
if (this.cachedRefs
|
||||
|
|
|
@ -164,7 +164,7 @@ suite('markdown: find all references', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('Should find references from link definition', async () => {
|
||||
test('Should find header references from link definition', async () => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`# A b C`,
|
||||
|
@ -466,6 +466,28 @@ suite('markdown: find all references', () => {
|
|||
}
|
||||
});
|
||||
|
||||
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');
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'mocha';
|
|||
import * as vscode from 'vscode';
|
||||
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
|
||||
import { MdReferencesProvider } from '../languageFeatures/references';
|
||||
import { MdRenameProvider } from '../languageFeatures/rename';
|
||||
import { MdRenameProvider, MdWorkspaceEdit } from '../languageFeatures/rename';
|
||||
import { githubSlugifier } from '../slugify';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
import { MdWorkspaceContents } from '../workspaceContents';
|
||||
|
@ -24,37 +24,62 @@ function prepareRename(doc: InMemoryDocument, pos: vscode.Position, workspaceCon
|
|||
const engine = createNewMarkdownEngine();
|
||||
const linkProvider = new MdLinkProvider(engine);
|
||||
const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier);
|
||||
const renameProvider = new MdRenameProvider(referencesProvider, 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) {
|
||||
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, githubSlugifier);
|
||||
return renameProvider.provideRenameEdits(doc, pos, newName, noopToken);
|
||||
const renameProvider = new MdRenameProvider(referencesProvider, workspaceContents, githubSlugifier);
|
||||
return renameProvider.provideRenameEditsImpl(doc, pos, newName, noopToken);
|
||||
}
|
||||
|
||||
function assertEditsEqual(actualEdit: vscode.WorkspaceEdit, ...expectedEdits: { uri: vscode.Uri; edits: vscode.TextEdit[] }[]) {
|
||||
const actualEntries = actualEdit.entries();
|
||||
assert.strictEqual(actualEntries.length, expectedEdits.length, `Reference counts should match`);
|
||||
interface ExpectedTextEdit {
|
||||
readonly uri: vscode.Uri;
|
||||
readonly edits: readonly vscode.TextEdit[];
|
||||
}
|
||||
|
||||
for (let i = 0; i < actualEntries.length; ++i) {
|
||||
const actual = actualEntries[i];
|
||||
const expected = expectedEdits[i];
|
||||
assert.strictEqual(actual[0].toString(), expected.uri.toString(), `Ref '${i}' has expected document`);
|
||||
interface ExpectedFileRename {
|
||||
readonly originalUri: vscode.Uri;
|
||||
readonly newUri: vscode.Uri;
|
||||
}
|
||||
|
||||
const actualEditForDoc = actual[1];
|
||||
const expectedEditsForDoc = expected.edits;
|
||||
assert.strictEqual(actualEditForDoc.length, expectedEditsForDoc.length, `Edit counts for '${actual[0]}' should match`);
|
||||
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`);
|
||||
}
|
||||
|
||||
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`);
|
||||
// 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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -325,24 +350,117 @@ suite('markdown: rename', () => {
|
|||
await assert.rejects(prepareRename(doc, new vscode.Position(1, 2), new InMemoryWorkspaceMarkdownDocuments([doc])));
|
||||
});
|
||||
|
||||
test('Rename should not be supported on bare file link', async () => {
|
||||
const uri = workspacePath('doc.md');
|
||||
const doc = new InMemoryDocument(uri, joinLines(
|
||||
`[text](./doc.md)`,
|
||||
`[other](./doc.md)`,
|
||||
));
|
||||
|
||||
await assert.rejects(prepareRename(doc, new vscode.Position(0, 10), new InMemoryWorkspaceMarkdownDocuments([doc])));
|
||||
});
|
||||
|
||||
test('Rename should not be supported on bare file link in definition', async () => {
|
||||
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`,
|
||||
));
|
||||
|
||||
await assert.rejects(prepareRename(doc, new vscode.Position(1, 10), new InMemoryWorkspaceMarkdownDocuments([doc])));
|
||||
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 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!, {
|
||||
originalUri: workspacePath('images', 'more', 'image.png'),
|
||||
newUri: workspacePath('img', 'test', 'new.png'),
|
||||
}, {
|
||||
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('Rename on link should use header text as placeholder', async () => {
|
||||
|
@ -357,7 +475,7 @@ suite('markdown: rename', () => {
|
|||
assertRangeEqual(info!.range, new vscode.Range(1, 8, 1, 13));
|
||||
});
|
||||
|
||||
test('Rename on http uri should work ', async () => {
|
||||
test('Rename on http uri should work', async () => {
|
||||
const uri1 = workspacePath('doc.md');
|
||||
const uri2 = workspacePath('doc2.md');
|
||||
const doc = new InMemoryDocument(uri1, joinLines(
|
||||
|
|
Loading…
Reference in a new issue