Compare commits

..

10 Commits

Author SHA1 Message Date
Riccardo Ferretti
4195797024 v0.17.2 2021-12-22 23:33:10 +01:00
Riccardo Ferretti
fa405f5f65 Preparation for 0.17.2 2021-12-22 23:32:27 +01:00
Riccardo Ferretti
4fd573b9e4 Fixed VS Code settings file 2021-12-22 23:11:19 +01:00
Riccardo Ferretti
f613e1b9e2 Fix issue when applying edits to last line
Authored by: @memeplex

See also #860
2021-12-22 23:11:03 +01:00
Riccardo Ferretti
0ada7d8e2c chore: minor change around test function 2021-12-22 22:53:51 +01:00
memeplex
8b39bcdf16 Update yarn.lock (#883) 2021-12-21 21:54:26 +01:00
memeplex
6073dc246d Remove legacy github slugger (#872) 2021-12-21 21:32:40 +01:00
memeplex
5b671d59a8 Use syntax injection for wikilinks (#876)
* Use syntax injection for wikilinks

* Configurable placeholder color

* Highlight only contents
2021-12-21 21:08:39 +01:00
memeplex
8abea48b5c Improve testing experience (#881)
* Improve testing experience
* Support vscode-jest for unit tests
2021-12-21 21:08:09 +01:00
Riccardo Ferretti
2eeb2e156b Fix #878 - Added support for (wiki)links in titles 2021-12-16 16:46:13 +01:00
27 changed files with 240 additions and 270 deletions

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ dist
docs/_site
docs/.sass-cache
docs/.jekyll-metadata
.test-workspace

38
.vscode/launch.json vendored
View File

@@ -6,15 +6,20 @@
"version": "0.2.0",
"configurations": [
{
"type": "node",
"name": "Debug Jest Tests",
"type": "extensionHost",
"request": "launch",
"runtimeArgs": ["workspace", "foam-vscode", "run", "test"], // ${yarnWorkspaceName} is what we're missing
"args": ["--runInBand"],
"runtimeExecutable": "yarn",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true
"args": [
"${workspaceFolder}/packages/foam-vscode/.test-workspace",
"--disable-extensions",
"--disable-workspace-trust",
"--extensionDevelopmentPath=${workspaceFolder}/packages/foam-vscode",
"--extensionTestsPath=${workspaceFolder}/packages/foam-vscode/out/test/suite"
],
"outFiles": [
"${workspaceFolder}/packages/foam-vscode/out/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
},
{
"name": "Run VSCode Extension",
@@ -24,8 +29,25 @@
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/packages/foam-vscode"
],
"outFiles": ["${workspaceFolder}/packages/foam-vscode/out/**/*.js"],
"outFiles": [
"${workspaceFolder}/packages/foam-vscode/out/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
},
{
"type": "node",
"name": "vscode-jest-tests",
"request": "launch",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"cwd": "${workspaceFolder}/packages/foam-vscode",
"runtimeExecutable": "yarn",
"args": [
"jest",
"--runInBand",
"--watchAll=false"
]
}
]
}

View File

@@ -24,9 +24,9 @@
"prettier.requireConfig": true,
"editor.formatOnSave": true,
"editor.tabSize": 2,
"jest.debugCodeLens.showWhenTestStateIn": ["fail", "unknown", "pass"],
"jest.autoRun": "off",
"jest.rootPath": "packages/foam-vscode",
"jest.jestCommandLine": "yarn jest",
"gitdoc.enabled": false,
"jest.autoEnable": false,
"jest.runAllTestsFirst": false,
"search.mode": "reuseEditor"
}

1
.yarnrc Normal file
View File

@@ -0,0 +1 @@
--ignore-engines true

View File

@@ -4,5 +4,5 @@
],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.17.1"
"version": "0.17.2"
}

View File

@@ -4,6 +4,16 @@ All notable changes to the "foam-vscode" extension will be documented in this fi
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [0.17.2] - 2021-12-22
Fixes and Improvements:
- Improved support for wikilinks in titles (#878)
- Use syntax injection for wikilinks (#876 - thanks @memeplex)
- Fix when applying text edits in last line
Internal:
- DX: Clean up of testing setup (#881 - thanks @memeplex)
## [0.17.1] - 2021-12-16
Fixes and Improvements:

View File

@@ -1,7 +0,0 @@
module.exports = {
presets: [
["@babel/preset-env", { targets: { node: "current" } }],
"@babel/preset-typescript"
],
plugins: [["@babel/plugin-transform-runtime", { helpers: false }]]
};

View File

@@ -82,7 +82,7 @@ module.exports = {
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
modulePathIgnorePatterns: ['.vscode-test'],
// Activates notifications for test results
// notify: false,
@@ -91,7 +91,7 @@ module.exports = {
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
preset: 'ts-jest',
// Run tests from one or more projects
// projects: undefined,
@@ -126,13 +126,13 @@ module.exports = {
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
setupFilesAfterEnv: ['jest-extended'],
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: "node"
testEnvironment: 'node',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
@@ -152,7 +152,10 @@ module.exports = {
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This is overridden in every runCLI invocation but it's here as the default
// for vscode-jest. We only want unit tests in the test explorer (sidebar),
// since spec tests require the entire extension host to be launched before.
testRegex: ['\\.test\\.ts$'],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,

View File

@@ -8,7 +8,7 @@
"type": "git"
},
"homepage": "https://github.com/foambubble/foam",
"version": "0.17.1",
"version": "0.17.2",
"license": "MIT",
"publisher": "foam",
"engines": {
@@ -37,6 +37,26 @@
"markdown.previewStyles": [
"./static/preview/style.css"
],
"grammars": [
{
"path": "./syntaxes/injection.json",
"scopeName": "foam.wikilink.injection",
"injectTo": [
"text.html.markdown"
]
}
],
"colors": [
{
"id": "foam.placeholder",
"description": "Color of foam placeholders.",
"defaults": {
"dark": "editorWarning.foreground",
"light": "editorWarning.foreground",
"highContrast": "editorWarning.foreground"
}
}
],
"views": {
"explorer": [
{
@@ -356,7 +376,9 @@
"build": "tsc -p ./",
"pretest": "yarn build",
"test": "node ./out/test/run-tests.js",
"pretest:unit": "yarn build",
"test:unit": "node ./out/test/run-tests.js --unit",
"pretest:e2e": "yarn build",
"test:e2e": "node ./out/test/run-tests.js --e2e",
"lint": "tsdx lint src",
"clean": "rimraf out",
@@ -377,7 +399,6 @@
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.10.4",
"@types/dateformat": "^3.0.1",
"@types/github-slugger": "^1.3.0",
"@types/glob": "^7.1.1",
"@types/lodash": "^4.14.157",
"@types/markdown-it": "^12.0.1",
@@ -388,12 +409,10 @@
"@types/vscode": "^1.47.1",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
"babel-jest": "^26.2.2",
"eslint": "^6.8.0",
"eslint-plugin-import": "^2.24.2",
"husky": "^4.2.5",
"jest": "^26.2.2",
"jest-environment-vscode": "^1.0.0",
"jest-extended": "^0.11.5",
"markdown-it": "^12.0.4",
"rimraf": "^3.0.2",
@@ -407,7 +426,6 @@
"dateformat": "^3.0.3",
"detect-newline": "^3.1.0",
"fast-array-diff": "^1.0.1",
"github-slugger": "^1.3.0",
"glob": "^7.1.6",
"gray-matter": "^4.0.2",
"lodash": "^4.17.21",

View File

@@ -34,5 +34,5 @@ const getOffset = (
offset = offset + lines[i].length + eolLen;
i++;
}
return offset + Math.min(position.character, lines[i].length);
return offset + Math.min(position.character, lines[i]?.length ?? 0);
};

View File

@@ -1,4 +1,3 @@
import GithubSlugger from 'github-slugger';
import { Resource } from '../model/note';
import { Range } from '../model/range';
import {
@@ -7,13 +6,10 @@ import {
} from '../markdown-provider';
import { getHeadingFromFileName } from '../utils';
import { FoamWorkspace } from '../model/workspace';
import { uriToSlug } from '../utils/slug';
export const LINK_REFERENCE_DEFINITION_HEADER = `[//begin]: # "Autogenerated link references for markdown compatibility"`;
export const LINK_REFERENCE_DEFINITION_FOOTER = `[//end]: # "Autogenerated link references"`;
const slugger = new GithubSlugger();
export interface TextEdit {
range: Range;
newText: string;
@@ -168,7 +164,7 @@ export const generateHeading = (note: Resource): TextEdit | null => {
return {
newText: `${paddingStart}# ${getHeadingFromFileName(
uriToSlug(note.uri)
note.uri.getName()
)}${paddingEnd}`,
range: Range.createFromPosition(
note.source.contentStart,
@@ -176,14 +172,3 @@ export const generateHeading = (note: Resource): TextEdit | null => {
),
};
};
/**
*
* @param fileName
* @returns null if file name is already in kebab case otherise returns
* the kebab cased file name
*/
export const getKebabCaseFileName = (fileName: string) => {
const kebabCasedFileName = slugger.slug(fileName);
return kebabCasedFileName === fileName ? null : kebabCasedFileName;
};

View File

@@ -5,11 +5,10 @@ import {
} from './markdown-provider';
import { DirectLink, WikiLink } from './model/note';
import { Logger } from './utils/log';
import { uriToSlug } from './utils/slug';
import { URI } from './model/uri';
import { FoamGraph } from './model/graph';
import { Range } from './model/range';
import { createTestWorkspace } from '../test/test-utils';
import { createTestWorkspace, getRandomURI } from '../test/test-utils';
Logger.setLevel('error');
@@ -40,50 +39,45 @@ const pageE = `
# Page E
`;
const createNoteFromMarkdown = (path: string, content: string) =>
createMarkdownParser([]).parse(URI.file(path), content);
const createNoteFromMarkdown = (content: string, path?: string) =>
createMarkdownParser([]).parse(
path ? URI.file(path) : getRandomURI(),
content
);
describe('Markdown loader', () => {
it('Converts markdown to notes', () => {
const workspace = createTestWorkspace();
workspace.set(createNoteFromMarkdown('/page-a.md', pageA));
workspace.set(createNoteFromMarkdown('/page-b.md', pageB));
workspace.set(createNoteFromMarkdown('/page-c.md', pageC));
workspace.set(createNoteFromMarkdown('/page-d.md', pageD));
workspace.set(createNoteFromMarkdown('/page-e.md', pageE));
workspace.set(createNoteFromMarkdown(pageA, '/page-a.md'));
workspace.set(createNoteFromMarkdown(pageB, '/page-b.md'));
workspace.set(createNoteFromMarkdown(pageC, '/page-c.md'));
workspace.set(createNoteFromMarkdown(pageD, '/page-d.md'));
workspace.set(createNoteFromMarkdown(pageE, '/page-e.md'));
expect(
workspace
.list()
.map(n => n.uri)
.map(uriToSlug)
.map(n => n.uri.getName())
.sort()
).toEqual(['page-a', 'page-b', 'page-c', 'page-d', 'page-e']);
});
it('Ingores external links', () => {
const note = createNoteFromMarkdown(
'/path/to/page-a.md',
`
this is a [link to google](https://www.google.com)
`
`this is a [link to google](https://www.google.com)`
);
expect(note.links.length).toEqual(0);
});
it('Ignores references to sections in the same file', () => {
const note = createNoteFromMarkdown(
'/path/to/page-a.md',
`
this is a [link to intro](#introduction)
`
`this is a [link to intro](#introduction)`
);
expect(note.links.length).toEqual(0);
});
it('Parses internal links correctly', () => {
const note = createNoteFromMarkdown(
'/path/to/page-a.md',
'this is a [link to page b](../doc/page-b.md)'
);
expect(note.links.length).toEqual(1);
@@ -95,7 +89,6 @@ this is a [link to intro](#introduction)
it('Parses links that have formatting in label', () => {
const note = createNoteFromMarkdown(
'/path/to/page-a.md',
'this is [**link** with __formatting__](../doc/page-b.md)'
);
expect(note.links.length).toEqual(1);
@@ -107,11 +100,11 @@ this is a [link to intro](#introduction)
it('Parses wikilinks correctly', () => {
const workspace = createTestWorkspace();
const noteA = createNoteFromMarkdown('/page-a.md', pageA);
const noteB = createNoteFromMarkdown('/page-b.md', pageB);
const noteC = createNoteFromMarkdown('/page-c.md', pageC);
const noteD = createNoteFromMarkdown('/Page D.md', pageD);
const noteE = createNoteFromMarkdown('/page e.md', pageE);
const noteA = createNoteFromMarkdown(pageA, '/page-a.md');
const noteB = createNoteFromMarkdown(pageB, '/page-b.md');
const noteC = createNoteFromMarkdown(pageC, '/page-c.md');
const noteD = createNoteFromMarkdown(pageD, '/Page D.md');
const noteE = createNoteFromMarkdown(pageE, '/page e.md');
workspace
.set(noteA)
@@ -134,7 +127,6 @@ this is a [link to intro](#introduction)
it('Parses backlinks with an alias', () => {
const note = createNoteFromMarkdown(
'/path/to/page-a.md',
'this is [[link|link alias]]. A link with spaces [[other link | spaced]]'
);
expect(note.links.length).toEqual(2);
@@ -151,9 +143,7 @@ this is a [link to intro](#introduction)
});
it('Skips wikilinks in codeblocks', () => {
const noteA = createNoteFromMarkdown(
'/dir1/page-a.md',
`
const noteA = createNoteFromMarkdown(`
this is some text with our [[first-wikilink]].
\`\`\`
@@ -161,8 +151,7 @@ this is inside a [[codeblock]]
\`\`\`
this is some text with our [[second-wikilink]].
`
);
`);
expect(noteA.links.map(l => l.label)).toEqual([
'first-wikilink',
'second-wikilink',
@@ -170,16 +159,13 @@ this is some text with our [[second-wikilink]].
});
it('Skips wikilinks in inlined codeblocks', () => {
const noteA = createNoteFromMarkdown(
'/dir1/page-a.md',
`
const noteA = createNoteFromMarkdown(`
this is some text with our [[first-wikilink]].
this is \`inside a [[codeblock]]\`
this is some text with our [[second-wikilink]].
`
);
`);
expect(noteA.links.map(l => l.label)).toEqual([
'first-wikilink',
'second-wikilink',
@@ -189,71 +175,71 @@ this is some text with our [[second-wikilink]].
describe('Note Title', () => {
it('should initialize note title if heading exists', () => {
const note = createNoteFromMarkdown(
'/page-a.md',
`
const note = createNoteFromMarkdown(`
# Page A
this note has a title
`
);
`);
expect(note.title).toBe('Page A');
});
it('should support wikilinks and urls in title', () => {
const note = createNoteFromMarkdown(`
# Page A with [[wikilink]] and a [url](https://google.com)
this note has a title
`);
expect(note.title).toBe('Page A with wikilink and a url');
});
it('should default to file name if heading does not exist', () => {
const note = createNoteFromMarkdown(
'/page-d.md',
`
This file has no heading.
`
`This file has no heading.`,
'/page-d.md'
);
expect(note.title).toEqual('page-d');
});
it('should give precedence to frontmatter title over other headings', () => {
const note = createNoteFromMarkdown(
'/page-e.md',
`
const note = createNoteFromMarkdown(`
---
title: Note Title
date: 20-12-12
---
# Other Note Title
`
);
`);
expect(note.title).toBe('Note Title');
});
it('should support numbers', () => {
const note1 = createNoteFromMarkdown('/157.md', `hello`);
const note1 = createNoteFromMarkdown(`hello`, '/157.md');
expect(note1.title).toBe('157');
const note2 = createNoteFromMarkdown('/157.md', `# 158`);
const note2 = createNoteFromMarkdown(`# 158`, '/157.md');
expect(note2.title).toBe('158');
const note3 = createNoteFromMarkdown(
'/157.md',
`
---
title: 159
---
# 158
`
`,
'/157.md'
);
expect(note3.title).toBe('159');
});
it('should not break on empty titles (see #276)', () => {
const note = createNoteFromMarkdown(
'/Hello Page.md',
`
#
this note has an empty title line
`
`,
'/Hello Page.md'
);
expect(note.title).toEqual('Hello Page');
});
@@ -261,47 +247,38 @@ this note has an empty title line
describe('frontmatter', () => {
it('should parse yaml frontmatter', () => {
const note = createNoteFromMarkdown(
'/page-e.md',
`
const note = createNoteFromMarkdown(`
---
title: Note Title
date: 20-12-12
---
# Other Note Title`
);
# Other Note Title`);
expect(note.properties.title).toBe('Note Title');
expect(note.properties.date).toBe('20-12-12');
});
it('should parse empty frontmatter', () => {
const note = createNoteFromMarkdown(
'/page-f.md',
`
const note = createNoteFromMarkdown(`
---
---
# Empty Frontmatter
`
);
`);
expect(note.properties).toEqual({});
});
it('should not fail when there are issues with parsing frontmatter', () => {
const note = createNoteFromMarkdown(
'/page-f.md',
`
const note = createNoteFromMarkdown(`
---
title: - one
- two
- #
---
`
);
`);
expect(note.properties).toEqual({});
});
@@ -310,11 +287,11 @@ title: - one
describe('wikilinks definitions', () => {
it('can generate links without file extension when includeExtension = false', () => {
const workspace = createTestWorkspace();
const noteA = createNoteFromMarkdown('/dir1/page-a.md', pageA);
const noteA = createNoteFromMarkdown(pageA, '/dir1/page-a.md');
workspace
.set(noteA)
.set(createNoteFromMarkdown('/dir1/page-b.md', pageB))
.set(createNoteFromMarkdown('/dir1/page-c.md', pageC));
.set(createNoteFromMarkdown(pageB, '/dir1/page-b.md'))
.set(createNoteFromMarkdown(pageC, '/dir1/page-c.md'));
const noExtRefs = createMarkdownReferences(workspace, noteA.uri, false);
expect(noExtRefs.map(r => r.url)).toEqual(['page-b', 'page-c']);
@@ -322,11 +299,11 @@ describe('wikilinks definitions', () => {
it('can generate links with file extension when includeExtension = true', () => {
const workspace = createTestWorkspace();
const noteA = createNoteFromMarkdown('/dir1/page-a.md', pageA);
const noteA = createNoteFromMarkdown(pageA, '/dir1/page-a.md');
workspace
.set(noteA)
.set(createNoteFromMarkdown('/dir1/page-b.md', pageB))
.set(createNoteFromMarkdown('/dir1/page-c.md', pageC));
.set(createNoteFromMarkdown(pageB, '/dir1/page-b.md'))
.set(createNoteFromMarkdown(pageC, '/dir1/page-c.md'));
const extRefs = createMarkdownReferences(workspace, noteA.uri, true);
expect(extRefs.map(r => r.url)).toEqual(['page-b.md', 'page-c.md']);
@@ -334,11 +311,11 @@ describe('wikilinks definitions', () => {
it('use relative paths', () => {
const workspace = createTestWorkspace();
const noteA = createNoteFromMarkdown('/dir1/page-a.md', pageA);
const noteA = createNoteFromMarkdown(pageA, '/dir1/page-a.md');
workspace
.set(noteA)
.set(createNoteFromMarkdown('/dir2/page-b.md', pageB))
.set(createNoteFromMarkdown('/dir3/page-c.md', pageC));
.set(createNoteFromMarkdown(pageB, '/dir2/page-b.md'))
.set(createNoteFromMarkdown(pageC, '/dir3/page-c.md'));
const extRefs = createMarkdownReferences(workspace, noteA.uri, true);
expect(extRefs.map(r => r.url)).toEqual([
@@ -350,13 +327,10 @@ describe('wikilinks definitions', () => {
describe('tags plugin', () => {
it('can find tags in the text of the note', () => {
const noteA = createNoteFromMarkdown(
'/dir1/page-a.md',
`
const noteA = createNoteFromMarkdown(`
# this is a #heading
#this is some #text that includes #tags we #care-about.
`
);
`);
expect(noteA.tags).toEqual([
{ label: 'heading', range: Range.create(1, 12, 1, 20) },
{ label: 'this', range: Range.create(2, 0, 2, 5) },
@@ -367,16 +341,13 @@ describe('tags plugin', () => {
});
it('will skip tags in codeblocks', () => {
const noteA = createNoteFromMarkdown(
'/dir1/page-a.md',
`
const noteA = createNoteFromMarkdown(`
this is some #text that includes #tags we #care-about.
\`\`\`
this is a #codeblock
\`\`\`
`
);
`);
expect(noteA.tags.map(t => t.label)).toEqual([
'text',
'tags',
@@ -385,13 +356,9 @@ this is a #codeblock
});
it('will skip tags in inlined codeblocks', () => {
const noteA = createNoteFromMarkdown(
'/dir1/page-a.md',
`
const noteA = createNoteFromMarkdown(`
this is some #text that includes #tags we #care-about.
this is a \`inlined #codeblock\`
`
);
this is a \`inlined #codeblock\` `);
expect(noteA.tags.map(t => t.label)).toEqual([
'text',
'tags',
@@ -399,16 +366,13 @@ this is a \`inlined #codeblock\`
]);
});
it('can find tags as text in yaml', () => {
const noteA = createNoteFromMarkdown(
'/dir1/page-a.md',
`
const noteA = createNoteFromMarkdown(`
---
tags: hello, world this_is_good
---
# this is a heading
this is some #text that includes #tags we #care-about.
`
);
`);
expect(noteA.tags.map(t => t.label)).toEqual([
'hello',
'world',
@@ -420,16 +384,13 @@ this is some #text that includes #tags we #care-about.
});
it('can find tags as array in yaml', () => {
const noteA = createNoteFromMarkdown(
'/dir1/page-a.md',
`
const noteA = createNoteFromMarkdown(`
---
tags: [hello, world, this_is_good]
---
# this is a heading
this is some #text that includes #tags we #care-about.
`
);
`);
expect(noteA.tags.map(t => t.label)).toEqual([
'hello',
'world',
@@ -444,16 +405,13 @@ this is some #text that includes #tags we #care-about.
// For now it's enough to just get the YAML block range
// in the future we might want to be more specific
const noteA = createNoteFromMarkdown(
'/dir1/page-a.md',
`
const noteA = createNoteFromMarkdown(`
---
tags: [hello, world, this_is_good]
---
# this is a heading
this is some text
`
);
`);
expect(noteA.tags[0]).toEqual({
label: 'hello',
range: Range.create(1, 0, 3, 3),
@@ -463,9 +421,7 @@ this is some text
describe('Sections plugin', () => {
it('should find sections within the note', () => {
const note = createNoteFromMarkdown(
'/dir1/page-a.md',
`
const note = createNoteFromMarkdown(`
# Section 1
This is the content of section 1.
@@ -477,8 +433,7 @@ This is the content of section 1.1.
# Section 2
This is the content of section 2.
`
);
`);
expect(note.sections).toHaveLength(3);
expect(note.sections[0].label).toEqual('Section 1');
expect(note.sections[0].range).toEqual(Range.create(1, 0, 9, 0));
@@ -487,6 +442,20 @@ This is the content of section 2.
expect(note.sections[2].label).toEqual('Section 2');
expect(note.sections[2].range).toEqual(Range.create(9, 0, 13, 0));
});
it('should support wikilinks and links in the section label', () => {
const note = createNoteFromMarkdown(`
# Section with [[wikilink]]
This is the content of section with wikilink
## Section with [url](https://google.com)
This is the content of section with url`);
expect(note.sections).toHaveLength(2);
expect(note.sections[0].label).toEqual('Section with wikilink');
expect(note.sections[1].label).toEqual('Section with url');
});
});
describe('parser plugins', () => {

View File

@@ -177,9 +177,9 @@ export class MarkdownResourceProvider implements ResourceProvider {
*/
const getTextFromChildren = (root: Node): string => {
let text = '';
visit(root, 'text', node => {
if (node.type === 'text') {
text = text + (node as any).value;
visit(root, node => {
if (node.type === 'text' || node.type === 'wikiLink') {
text = text + ((node as any).value || '');
}
});
return text;
@@ -226,7 +226,7 @@ const sectionsPlugin: ParserPlugin = {
visit: (node, note) => {
if (node.type === 'heading') {
const level = (node as any).depth;
const label = ((node as Parent)!.children?.[0] as any)?.value;
const label = getTextFromChildren(node);
if (!label || !level) {
return;
}
@@ -272,8 +272,8 @@ const titlePlugin: ParserPlugin = {
node.type === 'heading' &&
(node as any).depth === 1
) {
note.title =
((node as Parent)!.children?.[0] as any)?.value || note.title;
const title = getTextFromChildren(node);
note.title = title.length > 0 ? title : note.title;
}
},
onDidFindProperties: (props, note) => {

View File

@@ -1,5 +1,4 @@
import { Logger } from '../utils/log';
import { uriToSlug } from '../utils/slug';
import { URI } from './uri';
Logger.setLevel('error');
@@ -73,16 +72,4 @@ describe('Foam URI', () => {
URI.file('/hello.markdown')
);
});
it('can be slugified', () => {
expect(uriToSlug(URI.file('/this/is/a/path.md'))).toEqual('path');
expect(uriToSlug(URI.file('../a/relative/path.md'))).toEqual('path');
expect(uriToSlug(URI.file('another/relative/path.md'))).toEqual('path');
expect(uriToSlug(URI.file('no-directory.markdown'))).toEqual(
'no-directory'
);
expect(uriToSlug(URI.file('many.dots.name.markdown'))).toEqual(
'manydotsname'
);
});
});

View File

@@ -1,5 +0,0 @@
import GithubSlugger from 'github-slugger';
import { URI } from '../model/uri';
export const uriToSlug = (uri: URI): string =>
GithubSlugger.slug(uri.getName());

View File

@@ -8,21 +8,15 @@ import {
import { ResourceParser } from '../core/model/note';
import { FoamWorkspace } from '../core/model/workspace';
import { Foam } from '../core/model/foam';
import { Range } from '../core/model/range';
import { fromVsCodeUri } from '../utils/vsc-utils';
export const CONFIG_KEY = 'decorations.links.enable';
const linkDecoration = vscode.window.createTextEditorDecorationType({
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
textDecoration: 'none',
color: { id: 'textLink.foreground' },
cursor: 'pointer',
});
const placeholderDecoration = vscode.window.createTextEditorDecorationType({
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
textDecoration: 'none',
color: { id: 'editorWarning.foreground' },
color: { id: 'foam.placeholder' },
cursor: 'pointer',
});
@@ -42,17 +36,20 @@ const updateDecorations = (
fromVsCodeUri(editor.document.uri),
editor.document.getText()
);
let linkRanges = [];
let placeholderRanges = [];
note.links.forEach(link => {
const linkUri = workspace.resolveLink(note, link);
if (linkUri.isPlaceholder()) {
placeholderRanges.push(link.range);
} else {
linkRanges.push(link.range);
placeholderRanges.push(
Range.create(
link.range.start.line,
link.range.start.character + 2,
link.range.end.line,
link.range.end.character - 2
)
);
}
});
editor.setDecorations(linkDecoration, linkRanges);
editor.setDecorations(placeholderDecoration, placeholderRanges);
};
@@ -82,7 +79,6 @@ const feature: FoamFeature = {
context.subscriptions.push(
areDecorationsEnabled,
linkDecoration,
placeholderDecoration,
vscode.window.onDidChangeActiveTextEditor(editor => {
activeEditor = editor;

View File

@@ -69,7 +69,7 @@ describe('Document navigation', () => {
expect(links.length).toEqual(1);
expect(links[0].target).toEqual(OPEN_COMMAND.asURI(fileA.uri));
expect(links[0].range).toEqual(new vscode.Range(0, 18, 0, 28));
expect(links[0].range).toEqual(new vscode.Range(0, 20, 0, 26));
});
it('should create links for placeholders', async () => {
@@ -87,7 +87,7 @@ describe('Document navigation', () => {
expect(links[0].target).toEqual(
OPEN_COMMAND.asURI(URI.placeholder('a placeholder'))
);
expect(links[0].range).toEqual(new vscode.Range(0, 18, 0, 35));
expect(links[0].range).toEqual(new vscode.Range(0, 20, 0, 33));
});
});

View File

@@ -156,7 +156,12 @@ export class NavigationProvider
return targets.map(o => {
const command = OPEN_COMMAND.asURI(o.target);
const documentLink = new vscode.DocumentLink(
toVsCodeRange(o.link.range),
new vscode.Range(
o.link.range.start.line,
o.link.range.start.character + 2,
o.link.range.end.line,
o.link.range.end.character - 2
),
command
);
documentLink.tooltip = o.target.isPlaceholder()

View File

@@ -1,5 +1,3 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import { runTests } from 'vscode-test';
import { runUnit } from './suite-unit';
@@ -35,23 +33,17 @@ async function main() {
// The path to the extension test script
// Passed to --extensionTestsPath
const extensionTestsPath = path.join(__dirname, 'suite');
const tmpWorkspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'foam-'));
// Download VS Code, unzip it and run the integration test
await runTests({
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [
tmpWorkspaceDir,
path.join(extensionDevelopmentPath, '.test-workspace'),
'--disable-extensions',
'--disable-workspace-trust',
],
// Running the tests with vscode 1.53.0 is causing issues in the output/error stream management,
// which is causing a stack overflow, possibly due to a recursive callback.
// Also see https://github.com/foambubble/foam/pull/479#issuecomment-774167127
// Forcing the version to 1.52.0 solves the problem.
// TODO: to review, further investigate, and roll back this workaround.
version: '1.52.0',
version: '1.60.0',
});
} catch (err) {
console.log('Error occurred while running Foam e2e tests:', err);

View File

@@ -8,32 +8,28 @@
* they will make direct use of the vscode API to be invoked as commands, create editors,
* and so on..
*/
/* eslint-disable import/first */
// Set before imports, see https://github.com/facebook/jest/issues/12162
process.env.FORCE_COLOR = '1';
process.env.NODE_ENV = 'test';
import path from 'path';
import { runCLI } from '@jest/core';
const rootDir = path.join(__dirname, '..', '..');
export function runUnit(): Promise<void> {
process.env.FORCE_COLOR = '1';
process.env.NODE_ENV = 'test';
process.env.BABEL_ENV = 'test';
return new Promise(async (resolve, reject) => {
try {
const { results } = await runCLI(
{
rootDir,
roots: ['<rootDir>/src'],
transform: JSON.stringify({ '^.+\\.ts$': 'ts-jest' }),
runInBand: true,
testRegex: '\\.(test)\\.ts$',
setupFiles: ['<rootDir>/src/test/support/jest-setup.ts'],
setupFilesAfterEnv: ['jest-extended'],
globals: JSON.stringify({
'ts-jest': {
tsconfig: path.join(rootDir, 'tsconfig.json'),
},
}),
testTimeout: 20000,
verbose: false,
silent: false,

View File

@@ -9,42 +9,46 @@
* and so on..
*/
/* eslint-disable import/first */
// Set before imports, see https://github.com/facebook/jest/issues/12162
process.env.FORCE_COLOR = '1';
process.env.NODE_ENV = 'test';
import path from 'path';
import { runCLI } from '@jest/core';
import { cleanWorkspace } from './test-utils-vscode';
const rootDir = path.resolve(__dirname, '../..');
const rootDir = path.join(__dirname, '../..');
export function run(): Promise<void> {
const errWrite = process.stderr.write;
let remaining = '';
process.stderr.write = (buffer: string) => {
console.log(buffer);
const lines = (remaining + buffer).split('\n');
remaining = lines.pop() as string;
// Trim long lines because some uninformative code dumps will flood the
// console or, worse, be suppressed altogether because of their size.
lines.forEach(l => console.log(l.substr(0, 300)));
return true;
};
// process.on('unhandledRejection', err => {
// throw err;
// });
process.env.FORCE_COLOR = '1';
process.env.NODE_ENV = 'test';
process.env.BABEL_ENV = 'test';
return new Promise(async (resolve, reject) => {
await cleanWorkspace();
try {
const { results } = await runCLI(
{
rootDir,
roots: ['<rootDir>/src'],
transform: JSON.stringify({ '^.+\\.ts$': 'ts-jest' }),
runInBand: true,
testRegex: '\\.(test|spec)\\.ts$',
testEnvironment:
'<rootDir>/src/test/support/extended-vscode-environment.js',
testEnvironment: '<rootDir>/src/test/support/vscode-environment.js',
setupFiles: ['<rootDir>/src/test/support/jest-setup.ts'],
setupFilesAfterEnv: ['jest-extended'],
globals: JSON.stringify({
'ts-jest': {
tsconfig: path.resolve(rootDir, './tsconfig.json'),
},
}),
testTimeout: 30000,
useStderr: true,
verbose: true,
@@ -71,6 +75,7 @@ export function run(): Promise<void> {
return reject(error);
} finally {
process.stderr.write = errWrite.bind(process.stderr);
await cleanWorkspace();
}
});
}

View File

@@ -1,27 +1,27 @@
// Based on https://github.com/svsool/vscode-memo/blob/master/src/test/env/ExtendedVscodeEnvironment.js
const VscodeEnvironment = require('jest-environment-vscode');
const NodeEnvironment = require('jest-environment-node');
const vscode = require('vscode');
const initialVscode = vscode;
class ExtendedVscodeEnvironment extends VscodeEnvironment {
class VscodeEnvironment extends NodeEnvironment {
async setup() {
await super.setup();
this.global.vscode = vscode;
// Expose RegExp otherwise document.getWordRangeAtPosition won't work as supposed.
// Implementation of getWordRangeAtPosition uses "instanceof RegExp" which returns false
// due to Jest running tests in the different vm context.
// See https://github.com/nodejs/node-v0.x-archive/issues/1277.
// And also https://github.com/microsoft/vscode-test/issues/37#issuecomment-700167820
this.global.RegExp = RegExp;
this.global.vscode = vscode;
vscode.workspace
.getConfiguration()
.update('foam.edit.linkReferenceDefinitions', 'off');
}
async teardown() {
this.global.vscode = initialVscode;
this.global.vscode = {};
await super.teardown();
}
}
module.exports = ExtendedVscodeEnvironment;
module.exports = VscodeEnvironment;

View File

@@ -13,7 +13,7 @@ import { randomString, wait } from './test-utils';
Logger.setLevel('error');
export const cleanWorkspace = async () => {
const files = await vscode.workspace.findFiles('**', '.vscode');
const files = await vscode.workspace.findFiles('**', '{.vscode,.keep}');
await Promise.all(files.map(f => vscode.workspace.fs.delete(f)));
};

View File

@@ -109,3 +109,6 @@ export const randomString = (len = 5) =>
.fill('')
.map(() => chars.charAt(Math.floor(Math.random() * chars.length)))
.join('');
export const getRandomURI = () =>
URI.file('/random-uri-root/' + randomString() + '.md');

View File

@@ -0,0 +1,11 @@
{
"scopeName": "foam.wikilink.injection",
"injectionSelector": "L:meta.paragraph.markdown",
"patterns": [
{
"contentName": "string.other.link.title.markdown.foam",
"begin": "\\[\\[",
"end": "\\]\\]"
}
]
}

View File

@@ -2250,11 +2250,6 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
"@types/github-slugger@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@types/github-slugger/-/github-slugger-1.3.0.tgz#16ab393b30d8ae2a111ac748a015ac05a1fc5524"
integrity sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==
"@types/glob@^7.1.1":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
@@ -2954,7 +2949,7 @@ babel-jest@^24.9.0:
chalk "^2.4.2"
slash "^2.0.0"
babel-jest@^26.2.2, babel-jest@^26.6.3:
babel-jest@^26.6.3:
version "26.6.3"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056"
integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==
@@ -4283,11 +4278,6 @@ emittery@^0.7.1:
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82"
integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==
"emoji-regex@>=6.0.0 <=6.1.1":
version "6.1.1"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e"
integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@@ -5312,13 +5302,6 @@ gitconfiglocal@^1.0.0:
dependencies:
ini "^1.3.2"
github-slugger@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9"
integrity sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==
dependencies:
emoji-regex ">=6.0.0 <=6.1.1"
glob-parent@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
@@ -6515,11 +6498,6 @@ jest-environment-node@^26.6.2:
jest-mock "^26.6.2"
jest-util "^26.6.2"
jest-environment-vscode@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/jest-environment-vscode/-/jest-environment-vscode-1.0.0.tgz#96367fe8531047e64359e0682deafc973bfaea91"
integrity sha512-VKlj5j5pNurFEwWPaDiX1kBgmhWqcJTAZsvEX1x5lh0/+5myjk+qipEs/dPJVRbBPb3XFxiR48XzGn+wOU7SSQ==
jest-extended@^0.11.5:
version "0.11.5"
resolved "https://registry.yarnpkg.com/jest-extended/-/jest-extended-0.11.5.tgz#f063b3f1eaadad8d7c13a01f0dfe0f538d498ccf"