Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generate copy without wikilinks #1365

Merged
merged 24 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
60b4adf
inseart new test file
hereistheusername Jun 21, 2024
bab6cd7
add generate standalone note command
hereistheusername Jun 21, 2024
bcfde0e
vscode cmd configure
hereistheusername Jun 21, 2024
76ed1ad
fix embeded wikilinks
hereistheusername Jun 21, 2024
07702c2
refactor convertLinksFormat function & add 4 user command interfaces
hereistheusername Jun 23, 2024
62f458e
cleanup
hereistheusername Jun 23, 2024
0aefd72
add comments
hereistheusername Jun 23, 2024
2e350d7
change user interface
hereistheusername Jun 24, 2024
08451f1
del outdated comments
hereistheusername Jun 24, 2024
18dbbb5
fix typo
hereistheusername Jun 28, 2024
b384491
modify createUpdateLinkEdit to accomplish convert
hereistheusername Jun 28, 2024
7adf32f
only images can be embedded
hereistheusername Jun 29, 2024
4483132
move useAngles to if-branch uses it
hereistheusername Jun 29, 2024
8dd26c9
keey filename when using in page anchor
hereistheusername Jun 29, 2024
7732e4b
remove redundant procedure
hereistheusername Jun 29, 2024
8194b5c
give a default value to alias in link format combination branch
hereistheusername Jun 30, 2024
6765d52
add tests to createUpdateLinkEdit about changint links' type and isEmbed
hereistheusername Jun 30, 2024
1daf3be
follow prettier format
hereistheusername Jun 30, 2024
aaa8321
change to one line expression
hereistheusername Jun 30, 2024
bdfa676
get target from getIdentifier
hereistheusername Jul 1, 2024
7af316b
add tests for inserting angles
hereistheusername Jul 2, 2024
e8e3c2a
Merge pull request #1 from hereistheusername/test/modify-createUpdate…
hereistheusername Jul 4, 2024
b9775a3
fix typo
hereistheusername Jul 6, 2024
866a091
change require to import statement
hereistheusername Jul 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/foam-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,22 @@
"command": "foam-vscode.open-resource",
"title": "Foam: Open Resource"
},
{
"command": "foam-vscode.convert-wikilink-to-markdownlink-inplace",
"title": "Foam: convert wikilinks to markdown link format in place"
},
{
"command": "foam-vscode.convert-markdownlink-to-wikilink-inplace",
"title": "Foam: convert markdown links to wikilinks format in place"
},
{
"command": "foam-vscode.convert-wikilink-to-markdownlink-incopy",
"title": "Foam: convert wikilinks to markdown link format in copy"
},
{
"command": "foam-vscode.convert-markdownlink-to-wikilink-incopy",
"title": "Foam: convert markdown links to wikilinks format in copy"
},
{
"command": "foam-vscode.views.orphans.group-by:folder",
"title": "Group By Folder",
Expand Down
71 changes: 71 additions & 0 deletions packages/foam-vscode/src/core/janitor/convert-links-format.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { convertLinkFormat } from '.';
import { TEST_DATA_DIR } from '../../test/test-utils';
import { MarkdownResourceProvider } from '../services/markdown-provider';
import { Resource } from '../model/note';
import { FoamWorkspace } from '../model/workspace';
import { Logger } from '../utils/log';
import fs from 'fs';
import { URI } from '../model/uri';
import { createMarkdownParser } from '../services/markdown-parser';
import { FileDataStore } from '../../test/test-datastore';

Logger.setLevel('error');

describe('generateStdMdLink', () => {
let _workspace: FoamWorkspace;
// TODO slug must be reserved for actual slugs, not file names
const findBySlug = (slug: string): Resource => {
return _workspace
.list()
.find(res => res.uri.getName() === slug) as Resource;
};

beforeAll(async () => {
/** Use fs for reading files in units where vscode.workspace is unavailable */
const readFile = async (uri: URI) =>
(await fs.promises.readFile(uri.toFsPath())).toString();
const dataStore = new FileDataStore(
readFile,
TEST_DATA_DIR.joinPath('__scaffold__').toFsPath()
);
const parser = createMarkdownParser();
const mdProvider = new MarkdownResourceProvider(dataStore, parser);
_workspace = await FoamWorkspace.fromProviders([mdProvider], dataStore);
});

it('initialised test graph correctly', () => {
expect(_workspace.list().length).toEqual(11);
});

it('can generate markdown links correctly', async () => {
const note = findBySlug('file-with-different-link-formats');
const actual = note.links
.filter(link => link.type === 'wikilink')
.map(link => convertLinkFormat(link, 'link', _workspace, note));
const expected: string[] = [
'[first-document](first-document.md)',
'[second-document](second-document.md)',
'[[non-exist-file]]',
'[#one section](<#one section>)',
'[another name](<#one section>)',
'[an alias](first-document.md)',
'[first-document](first-document.md)',
];
expect(actual.length).toEqual(expected.length);
const _ = actual.map((LinkReplace, index) => {
expect(LinkReplace.newText).toEqual(expected[index]);
});
});

it('can generate wikilinks correctly', async () => {
const note = findBySlug('file-with-different-link-formats');
const actual = note.links
.filter(link => link.type === 'link')
.map(link => convertLinkFormat(link, 'wikilink', _workspace, note));
const expected: string[] = ['[[first-document|file]]'];
expect(actual.length).toEqual(expected.length);
const _ = actual.map((LinkReplace, index) => {
expect(LinkReplace.newText).toEqual(expected[index]);
});
});
});
103 changes: 103 additions & 0 deletions packages/foam-vscode/src/core/janitor/convert-links-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Resource, ResourceLink } from '../model/note';
import { URI } from '../model/uri';
import { Range } from '../model/range';
import { FoamWorkspace } from '../model/workspace';
import { isNone } from '../utils';
import { MarkdownLink } from '../services/markdown-link';

export interface LinkReplace {
newText: string;
range: Range /* old range */;
}

/**
* convert a link based on its workspace and the note containing it.
* According to targetFormat parameter to decide output format. If link.type === targetFormat, then it simply copy
* the rawText into LinkReplace. Therefore, it's recommanded to filter before conversion.

Check warning on line 16 in packages/foam-vscode/src/core/janitor/convert-links-format.ts

View workflow job for this annotation

GitHub Actions / Spell Check with Typos

"recommanded" should be "recommended".
* If targetFormat isn't supported, or the target resource pointed by link cannot be found, the function will throw
* exception.
* @param link
* @param targetFormat
* @param workspace
* @param note
* @returns LinkReplace { newText: string; range: Range; }
*/
export function convertLinkFormat(
link: ResourceLink,
targetFormat: string,
riccardoferretti marked this conversation as resolved.
Show resolved Hide resolved
workspace: FoamWorkspace,
note: Resource | URI
): LinkReplace {
const resource = note instanceof URI ? workspace.find(note) : note;
const targetUri = workspace.resolveLink(resource, link);
/* If it's already the target format or a placeholder, no transformation happens */
if (link.type === targetFormat || targetUri.scheme === 'placeholder') {
return {
newText: link.rawText,
range: link.range,
};
}

let { target, section, alias } = MarkdownLink.analyzeLink(link);
let sectionDivider = section ? '#' : '';
let aliasDivider = alias ? '|' : '';
let embed = link.isEmbed ? '!' : '';

if (isNone(targetUri)) {
throw new Error(
`Unexpected state: link to: "${link.rawText}" is not resolvable`
);
}

const targetRes = workspace.find(targetUri);
let relativeUri = targetRes.uri.relativeTo(resource.uri.getDirectory());

if (targetFormat === 'wikilink') {
/* remove extension if possible, then get the basename to prevent from filename conflict */
if (relativeUri.path.endsWith(workspace.defaultExtension)) {
relativeUri = relativeUri.changeExtension('*', '');
}
target = relativeUri.getBasename();

return {
newText: `${embed}[[${target}${sectionDivider}${section}${aliasDivider}${alias}]]`,
range: link.range,
};
}

if (targetFormat === 'link') {
/* if alias is empty, construct one as target#section */
if (alias === '') {
/* in page anchor have no filename */
if (relativeUri.getBasename() === resource.uri.getBasename()) {
target = '';
}
alias = `${target}${sectionDivider}${section}`;
}

/* construct url */
let url = relativeUri.path;
/* in page anchor have no filename */
if (relativeUri.getBasename() === resource.uri.getBasename()) {
url = '';
}
if (sectionDivider === '#') {
url = `${url}${sectionDivider}${section}`;
}
if (url.indexOf(' ') > 0) {
url = `<${url}>`;
}

/* if it's originally an embeded note, the markdown link shouldn't be embeded */

Check warning on line 91 in packages/foam-vscode/src/core/janitor/convert-links-format.ts

View workflow job for this annotation

GitHub Actions / Spell Check with Typos

"embeded" should be "embedded".

Check warning on line 91 in packages/foam-vscode/src/core/janitor/convert-links-format.ts

View workflow job for this annotation

GitHub Actions / Spell Check with Typos

"embeded" should be "embedded".
if (embed && targetRes.type === 'note') {
embed = '';
}
return {
newText: `${embed}[${alias}](${url})`,
range: link.range,
};
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested it locally, but I wonder if you could be able to replace this whole section with

const newLink = {...link, type: targetType}
const edit = MarkdownLink.createUpdateLinkEdit(newLink)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's impossible. The first line of MarkdownLink.createUpdateLinkEdit is

const { target, section, alias } = MarkdownLink.analyzeLink(link);

which extracts three components via regex from link.rawText based on link.type. If the pattern doesn't match, three of them may not be extracted correctly.

Moreover, when dealing with conversion from wikilink to link, we have to retrieve the relative path to target, which is usually a basename in wikilink's rawText. Therefore, we must have workspace and note: Resource | URI.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good point. Basically instead increateUpdateLinkEdit we need to add type?: 'wikilink' | 'link' to the delta parameter, and take it from there. Does that sound good?

Copy link
Contributor Author

@hereistheusername hereistheusername Jun 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is one possible version. I added type and isEmbed to the delta, which slightly affacts how createUpdateLinkEdit composes newText. Please check it.

differ to current code
branch

throw new Error(
`Unexpected state: targetFormat: ${targetFormat} is not supported`
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('generateLinkReferences', () => {
});

it('initialised test graph correctly', () => {
expect(_workspace.list().length).toEqual(10);
expect(_workspace.list().length).toEqual(11);
});

it('should add link references to a file that does not have them', async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/foam-vscode/src/core/janitor/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { generateLinkReferences } from './generate-link-references';
export { generateHeading } from './generate-headings';
export { convertLinkFormat } from './convert-links-format';
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { commands, ExtensionContext, window } from 'vscode';
import { isMdEditor } from '../../utils';
import { Foam } from '../../core/model/foam';
import { FoamWorkspace } from '../../core/model/workspace';
import { fromVsCodeUri, toVsCodeRange } from '../../utils/vsc-utils';
import { ResourceParser } from '../../core/model/note';
import { IMatcher } from '../../core/services/datastore';
import { convertLinkFormat } from '../../core/janitor';
const vscode = require('vscode'); /* cannot import workspace from above statement and not sure what happened */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you try again to import workspace from vscode, or use import * from vscode and use that?
I am not introducing the require

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


enum ConvertOption {
Wikilink2MDlink,
MDlink2Wikilink,
}

interface IConfig {
from: string;
to: string;
}

const Config: { [key in ConvertOption]: IConfig } = {
[ConvertOption.Wikilink2MDlink]: {
from: 'wikilink',
to: 'link',
},
[ConvertOption.MDlink2Wikilink]: {
from: 'link',
to: 'wikilink',
},
};

export default async function activate(
context: ExtensionContext,
foamPromise: Promise<Foam>
) {
const foam = await foamPromise;

/*
commands:
foam-vscode.convert-wikilink-to-markdownlink-inplace
foam-vscode.convert-markdownlink-to-wikilink-inplace
foam-vscode.convert-wikilink-to-markdownlink-in-copy
foam-vscode.convert-markdownlink-to-wikilink-in-copy
*/
riccardoferretti marked this conversation as resolved.
Show resolved Hide resolved
context.subscriptions.push(
commands.registerCommand(
'foam-vscode.convert-wikilink-to-markdownlink-inplace',
() => {
return convertLinkInPlace(
foam.workspace,
foam.services.parser,
foam.services.matcher,
Config[ConvertOption.Wikilink2MDlink]
);
}
),
commands.registerCommand(
'foam-vscode.convert-markdownlink-to-wikilink-inplace',
() => {
return convertLinkInPlace(
foam.workspace,
foam.services.parser,
foam.services.matcher,
Config[ConvertOption.MDlink2Wikilink]
);
}
),
commands.registerCommand(
'foam-vscode.convert-wikilink-to-markdownlink-incopy',
() => {
return convertLinkInCopy(
foam.workspace,
foam.services.parser,
foam.services.matcher,
Config[ConvertOption.Wikilink2MDlink]
);
}
),
commands.registerCommand(
'foam-vscode.convert-markdownlink-to-wikilink-incopy',
() => {
return convertLinkInCopy(
foam.workspace,
foam.services.parser,
foam.services.matcher,
Config[ConvertOption.MDlink2Wikilink]
);
}
)
);
}

/**
* convert links based on its workspace and the note containing it.
* Changes happen in-place
* @param fWorkspace
* @param fParser
* @param fMatcher
* @param convertOption
* @returns void
*/
async function convertLinkInPlace(
fWorkspace: FoamWorkspace,
fParser: ResourceParser,
fMatcher: IMatcher,
convertOption: IConfig
) {
const editor = window.activeTextEditor;
const doc = editor.document;

if (!isMdEditor(editor) || !fMatcher.isMatch(fromVsCodeUri(doc.uri))) {
return;
}
// const eol = getEditorEOL();
let text = doc.getText();

const resource = fParser.parse(fromVsCodeUri(doc.uri), text);

const textReplaceArr = resource.links
.filter(link => link.type === convertOption.from)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(note: in my proposed approach, this check would be link.type !== convertOption.to)

.map(link =>
convertLinkFormat(link, convertOption.to, fWorkspace, resource)
)
/* transform .range property into vscode range */
.map(linkReplace => ({
...linkReplace,
range: toVsCodeRange(linkReplace.range),
}));

/* reorder the array such that the later range comes first */
textReplaceArr.sort((a, b) => b.range.start.compareTo(a.range.start));

await editor.edit(editorBuilder => {
textReplaceArr.forEach(edit => {
editorBuilder.replace(edit.range, edit.newText);
});
});
}

/**
* convert links based on its workspace and the note containing it.
* Changes happen in a copy
* 1. prepare a copy file, and makt it the activeTextEditor
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makt?

* 2. call to convertLinkInPlace
* @param fWorkspace
* @param fParser
* @param fMatcher
* @param convertOption
* @returns void
*/
async function convertLinkInCopy(
fWorkspace: FoamWorkspace,
fParser: ResourceParser,
fMatcher: IMatcher,
convertOption: IConfig
) {
const editor = window.activeTextEditor;
const doc = editor.document;

if (!isMdEditor(editor) || !fMatcher.isMatch(fromVsCodeUri(doc.uri))) {
return;
}
// const eol = getEditorEOL();
let text = doc.getText();

const resource = fParser.parse(fromVsCodeUri(doc.uri), text);
const basePath = doc.uri.path.split('/').slice(0, -1).join('/');

const fileUri = vscode.Uri.file(
`${
basePath ? basePath + '/' : ''
}${resource.uri.getName()}.copy${resource.uri.getExtension()}`
);
const encoder = new TextEncoder();
await vscode.workspace.fs.writeFile(fileUri, encoder.encode(text));
await window.showTextDocument(fileUri);

await convertLinkInPlace(fWorkspace, fParser, fMatcher, convertOption);
}
1 change: 1 addition & 0 deletions packages/foam-vscode/src/features/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { default as openResource } from './open-resource';
export { default as updateGraphCommand } from './update-graph';
export { default as updateWikilinksCommand } from './update-wikilinks';
export { default as createNote } from './create-note';
export { default as generateStandaloneNote } from './convert-links-format-in-note';
Loading
Loading