Skip to content

Commit

Permalink
Support rename of the links with unknown extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
svsool committed Jul 22, 2020
1 parent b56dcb1 commit a6d2a21
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 48 deletions.
60 changes: 60 additions & 0 deletions src/extensions/ReferenceHoverProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,66 @@ describe('ReferenceHoverProvider', () => {
});
});

it('should provide hover with a warning about unknown extension', async () => {
const name0 = rndName();
const name1 = rndName();

await createFile(`${name0}.md`, `[[${name1}.unknown]]`);
await createFile(`${name1}.unknown`, '# Hello world');

const doc = await openTextDocument(`${name0}.md`);

const referenceHoverProvider = new ReferenceHoverProvider();

expect(referenceHoverProvider.provideHover(doc, new vscode.Position(0, 4)))
.toMatchInlineSnapshot(`
B {
"contents": Array [
"Link contains unknown extension: .unknown. Please use common file extensions .md,.png,.jpg,.jpeg,.svg,.gif,.doc,.docx,.rtf,.txt,.odt,.xls,.xlsx,.ppt,.pptm,.pptx,.pdf to get full support.",
],
"range": Array [
Object {
"character": 2,
"line": 0,
},
Object {
"character": 15,
"line": 0,
},
],
}
`);
});

it('should provide hover with a warning that file is not created yet', async () => {
const name0 = rndName();

await createFile(`${name0}.md`, `[[any-link]]`);

const doc = await openTextDocument(`${name0}.md`);

const referenceHoverProvider = new ReferenceHoverProvider();

expect(referenceHoverProvider.provideHover(doc, new vscode.Position(0, 4)))
.toMatchInlineSnapshot(`
B {
"contents": Array [
"\\"any-link\\" is not created yet. Click to create.",
],
"range": Array [
Object {
"character": 2,
"line": 0,
},
Object {
"character": 10,
"line": 0,
},
],
}
`);
});

it('should not provide hover for a link within code span', async () => {
const name0 = rndName();
const name1 = rndName();
Expand Down
25 changes: 18 additions & 7 deletions src/extensions/ReferenceHoverProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import path from 'path';

import {
containsImageExt,
containsUnknownExt,
containsOtherKnownExts,
getWorkspaceCache,
getConfigProperty,
getReferenceAtPosition,
isUncPath,
findUriByRef,
commonExts,
} from '../utils';

export default class ReferenceHoverProvider implements vscode.HoverProvider {
Expand All @@ -18,16 +20,25 @@ export default class ReferenceHoverProvider implements vscode.HoverProvider {

if (refAtPos) {
const { ref, range } = refAtPos;
const uris = getWorkspaceCache().allUris;
const imagePreviewMaxHeight = Math.max(getConfigProperty('imagePreviewMaxHeight', 200), 10);

const foundUri = findUriByRef(uris, ref);

const hoverRange = new vscode.Range(
new vscode.Position(range.start.line, range.start.character + 2),
new vscode.Position(range.end.line, range.end.character - 2),
);

if (containsUnknownExt(ref)) {
return new vscode.Hover(
`Link contains unknown extension: ${
path.parse(ref).ext
}. Please use common file extensions ${commonExts} to get full support.`,
hoverRange,
);
}

const uris = getWorkspaceCache().allUris;
const imagePreviewMaxHeight = Math.max(getConfigProperty('imagePreviewMaxHeight', 200), 10);

const foundUri = findUriByRef(uris, ref);

if (foundUri && fs.existsSync(foundUri.fsPath)) {
const getContent = () => {
if (containsImageExt(foundUri.fsPath)) {
Expand All @@ -45,9 +56,9 @@ export default class ReferenceHoverProvider implements vscode.HoverProvider {
};

return new vscode.Hover(getContent(), hoverRange);
} else {
return new vscode.Hover(`"${ref}" is not created yet. Click to create.`, hoverRange);
}

return new vscode.Hover(`"${ref}" is not created yet. Click to create.`, hoverRange);
}

return null;
Expand Down
69 changes: 58 additions & 11 deletions src/extensions/ReferenceRenameProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ describe('ReferenceRenameProvider', () => {

const referenceRenameProvider = new ReferenceRenameProvider();

expect(() =>
await expect(
referenceRenameProvider.prepareRename(doc, new vscode.Position(0, 2)),
).toThrowError('Rename is not available for nonexistent links.');
).rejects.toThrow('Rename is not available for nonexistent links.');
});

it('should not provide rename for file with unsaved changes', async () => {
Expand All @@ -40,9 +40,9 @@ describe('ReferenceRenameProvider', () => {

const referenceRenameProvider = new ReferenceRenameProvider();

expect(() =>
await expect(
referenceRenameProvider.prepareRename(doc, new vscode.Position(0, 2)),
).toThrowError('Rename is not available for unsaved files.');
).rejects.toThrow('Rename is not available for unsaved files.');
});

it('should not provide rename for multiline link', async () => {
Expand All @@ -54,9 +54,9 @@ describe('ReferenceRenameProvider', () => {

const referenceRenameProvider = new ReferenceRenameProvider();

expect(() =>
await expect(
referenceRenameProvider.prepareRename(doc, new vscode.Position(0, 2)),
).toThrowError('Rename is not available.');
).rejects.toThrow('Rename is not available.');
});

it('should provide rename for a link to the existing file', async () => {
Expand All @@ -70,7 +70,7 @@ describe('ReferenceRenameProvider', () => {

const referenceRenameProvider = new ReferenceRenameProvider();

expect(referenceRenameProvider.prepareRename(doc, new vscode.Position(0, 2)))
expect(await referenceRenameProvider.prepareRename(doc, new vscode.Position(0, 2)))
.toMatchInlineSnapshot(`
Array [
Object {
Expand All @@ -85,6 +85,32 @@ describe('ReferenceRenameProvider', () => {
`);
});

it('should provide rename for a link to the existing file with an unknown extension', async () => {
const name0 = rndName();
const name1 = rndName();

await createFile(`${name0}.md`, `[[${name1}.unknown]]`);
await createFile(`${name1}.unknown`);

const doc = await openTextDocument(`${name0}.md`);

const referenceRenameProvider = new ReferenceRenameProvider();

expect(await referenceRenameProvider.prepareRename(doc, new vscode.Position(0, 2)))
.toMatchInlineSnapshot(`
Array [
Object {
"character": 2,
"line": 0,
},
Object {
"character": 15,
"line": 0,
},
]
`);
});

it('should provide rename edit', async () => {
const name0 = rndName();
const name1 = rndName();
Expand All @@ -106,6 +132,27 @@ describe('ReferenceRenameProvider', () => {
expect(workspaceEdit!).not.toBeNull();
});

it('should provide rename edit for a link to the existing file with unknown extension', async () => {
const name0 = rndName();
const name1 = rndName();
const newLinkName = rndName();

await createFile(`${name0}.md`, `[[${name1}.unknown]]`);
await createFile(`${name1}.unknown`);

const doc = await openTextDocument(`${name0}.md`);

const referenceRenameProvider = new ReferenceRenameProvider();

const workspaceEdit = await referenceRenameProvider.provideRenameEdits(
doc,
new vscode.Position(0, 2),
newLinkName,
);

expect(workspaceEdit!).not.toBeNull();
});

it('should not provide rename for a link within code span', async () => {
const name0 = rndName();
const name1 = rndName();
Expand All @@ -117,9 +164,9 @@ describe('ReferenceRenameProvider', () => {

const referenceRenameProvider = new ReferenceRenameProvider();

expect(() =>
await expect(
referenceRenameProvider.prepareRename(doc, new vscode.Position(0, 2)),
).toThrowError('Rename is not available.');
).rejects.toThrow('Rename is not available.');
});

it('should not provide rename for a link within fenced code block', async () => {
Expand All @@ -142,8 +189,8 @@ describe('ReferenceRenameProvider', () => {

const referenceRenameProvider = new ReferenceRenameProvider();

expect(() =>
await expect(
referenceRenameProvider.prepareRename(doc, new vscode.Position(0, 2)),
).toThrowError('Rename is not available.');
).rejects.toThrow('Rename is not available.');
});
});
46 changes: 30 additions & 16 deletions src/extensions/ReferenceRenameProvider.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
import {
RenameProvider,
TextDocument,
ProviderResult,
Position,
Range,
WorkspaceEdit,
Uri,
} from 'vscode';
import { RenameProvider, TextDocument, Position, Range, WorkspaceEdit, Uri } from 'vscode';
import path from 'path';

import {
Expand All @@ -15,15 +7,19 @@ import {
getWorkspaceCache,
isLongRef,
getWorkspaceFolder,
containsUnknownExt,
findFilesByExts,
extractExt,
sortPaths,
} from '../utils';

const openingBracketsLength = 2;

export default class ReferenceRenameProvider implements RenameProvider {
public prepareRename(
public async prepareRename(
document: TextDocument,
position: Position,
): ProviderResult<Range | { range: Range; placeholder: string }> {
): Promise<Range | { range: Range; placeholder: string }> {
if (document.isDirty) {
throw new Error('Rename is not available for unsaved files. Please save your changes first.');
}
Expand All @@ -33,7 +29,16 @@ export default class ReferenceRenameProvider implements RenameProvider {
if (refAtPos) {
const { range, ref } = refAtPos;

if (!findUriByRef(getWorkspaceCache().allUris, ref)) {
const unknownUris = containsUnknownExt(ref) ? await findFilesByExts([extractExt(ref)]) : [];

const augmentedUris = unknownUris.length
? sortPaths([...getWorkspaceCache().allUris, ...unknownUris], {
pathKey: 'path',
shallowFirst: true,
})
: getWorkspaceCache().allUris;

if (!findUriByRef(augmentedUris, ref)) {
throw new Error(
'Rename is not available for nonexistent links. Create file first by clicking on the link.',
);
Expand All @@ -48,22 +53,31 @@ export default class ReferenceRenameProvider implements RenameProvider {
throw new Error('Rename is not available. Please try when focused on the link.');
}

public provideRenameEdits(
public async provideRenameEdits(
document: TextDocument,
position: Position,
newName: string,
): ProviderResult<WorkspaceEdit> {
): Promise<WorkspaceEdit> {
const refAtPos = getReferenceAtPosition(document, position);

if (refAtPos) {
const { ref } = refAtPos;

const workspaceEdit = new WorkspaceEdit();

const fsPath = findUriByRef(getWorkspaceCache().allUris, ref)?.fsPath;
const unknownUris = containsUnknownExt(ref) ? await findFilesByExts([extractExt(ref)]) : [];

const augmentedUris = unknownUris.length
? sortPaths([...getWorkspaceCache().allUris, ...unknownUris], {
pathKey: 'path',
shallowFirst: true,
})
: getWorkspaceCache().allUris;

const fsPath = findUriByRef(augmentedUris, ref)?.fsPath;

if (fsPath) {
const newRelativePath = `${newName}${!newName.includes('.') ? '.md' : ''}`;
const newRelativePath = `${newName}${path.parse(newName).ext === '' ? '.md' : ''}`;
const newUri = Uri.file(
isLongRef(ref)
? path.join(getWorkspaceFolder()!, newRelativePath)
Expand Down
Loading

0 comments on commit a6d2a21

Please sign in to comment.