Compare commits

..

24 Commits

Author SHA1 Message Date
Riccardo Ferretti
5f7b3b7c02 v0.15.4 2021-11-09 00:34:18 +01:00
Riccardo Ferretti
9ed0d6e18e prepare 0.15.4 2021-11-09 00:33:45 +01:00
Riccardo Ferretti
0140748550 improved URI.toFsPath 2021-11-09 00:24:53 +01:00
Riccardo
356dcc5579 Consolidate use of Foam URI (#820)
* always convert vscode.Uri to foam.URI

* Improve handling on Windows paths in URI

- convert to upper case drive letter
- normalize use of Windows conversion in URI
- added more test cases

* Fixed tests
2021-11-08 23:39:01 +01:00
Riccardo Ferretti
265afdee19 v0.15.3 2021-11-08 11:34:18 +01:00
Riccardo Ferretti
de7c686f75 Prepare 0.15.3 2021-11-08 11:34:05 +01:00
Riccardo
8dfc5bd2ff Throw exception instead of process.exit (#819) 2021-11-08 11:12:15 +01:00
Riccardo
b3c5e75aa2 Fixing some test issues (#818)
* renamed test scripts

* improved hover provider tests

* removed buffering of log lines in test suite
2021-11-06 17:48:27 +01:00
Paul de Raaij
000da4bd1c Allow inclusion of note when using reference definitions (#808)
* Allow inclusion of note when using reference definitions

* Add additional comments
2021-11-04 20:17:03 +01:00
allcontributors[bot]
86749940c2 docs: add AndreiD049 as a contributor for code (#815)
* docs: update docs/index.md [skip ci]

* docs: update readme.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-11-04 13:09:09 +01:00
AndreiD049
27f9a08870 replaced vscode uri with foam uri when generating references (#814) 2021-11-04 11:58:15 +01:00
Riccardo Ferretti
e791726692 fixed logging in test suite 2021-11-03 10:54:15 +01:00
Riccardo Ferretti
a3c00744ca fixed linting errors 2021-11-03 10:52:27 +01:00
allcontributors[bot]
00220b1f6c docs: add memeplex as a contributor for code (#812)
* docs: update docs/index.md [skip ci]

* docs: update readme.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-11-03 10:31:56 +01:00
memeplex
759f4f1963 Avoid delaying decorations on editor switch (#811) 2021-11-03 10:31:20 +01:00
Riccardo Ferretti
d86fc7f433 removed outdated use of links 2021-11-01 20:04:37 +01:00
Riccardo
bd9c6806fa tweaks to test suite (#804) 2021-10-28 23:13:20 +02:00
Riccardo Ferretti
4c9a9cec56 v0.15.2 2021-10-27 12:11:02 +02:00
Riccardo Ferretti
8a91a6ab36 Prepare v0.15.2 2021-10-27 12:05:39 +02:00
Paul de Raaij
667037bc14 Improve generation of link reference definitions (#786)
* Fixes the removal of explicitly defined link references

* Add use case of explicit & implicit
2021-10-27 10:58:10 +02:00
allcontributors[bot]
30cc9fc9f0 docs: add eltociear as a contributor for doc (#801)
* docs: update docs/index.md [skip ci]

* docs: update readme.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-10-27 10:52:41 +02:00
Ikko Ashimine
abed7be3ec Fix typo in write-your-notes-in-github-gist.md (#798)
recieve -> receive
2021-10-27 10:51:27 +02:00
Riccardo
d31e094358 Added support for target date variables in daily note template (#781)
* added support for target date variables in daily note template

* added FOAM_DATE_* variables to resolver

* Document `FOAM_DATE_*` template variables

* Add CHANGELOG entry

Co-authored-by: Michael Overmeyer <michael.overmeyer@shopify.com>
2021-10-27 10:50:58 +02:00
Riccardo
f320af05c5 Improve graph performance by batching painting (#795) 2021-10-26 13:01:19 +02:00
47 changed files with 881 additions and 304 deletions

View File

@@ -761,6 +761,33 @@
"contributions": [
"doc"
]
},
{
"login": "eltociear",
"name": "Ikko Ashimine",
"avatar_url": "https://avatars.githubusercontent.com/u/22633385?v=4",
"profile": "https://bandism.net/",
"contributions": [
"doc"
]
},
{
"login": "memeplex",
"name": "memeplex",
"avatar_url": "https://avatars.githubusercontent.com/u/2845433?v=4",
"profile": "https://github.com/memeplex",
"contributions": [
"code"
]
},
{
"login": "AndreiD049",
"name": "AndreiD049",
"avatar_url": "https://avatars.githubusercontent.com/u/52671223?v=4",
"profile": "https://github.com/AndreiD049",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@@ -41,12 +41,42 @@ Templates can use all the variables available in [VS Code Snippets](https://code
In addition, you can also use variables provided by Foam:
| Name | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FOAM_SELECTED_TEXT` | Foam will fill it with selected text when creating a new note, if any text is selected. Selected text will be replaced with a wikilink to the new note. |
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
| Name | Description |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FOAM_SELECTED_TEXT` | Foam will fill it with selected text when creating a new note, if any text is selected. Selected text will be replaced with a wikilink to the new note. |
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |
| `FOAM_DATE_*` | `FOAM_DATE_YEAR`, `FOAM_DATE_MONTH`, etc. Foam-specific versions of [VS Code's datetime snippet variables](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables). Prefer these versions over VS Code's. |
**Note:** neither the defaulting feature (eg. `${variable:default}`) nor the format feature (eg. `${variable/(.*)/${1:/upcase}/}`) (available to other variables) are available for these Foam-provided variables.
**Note:** neither the defaulting feature (eg. `${variable:default}`) nor the format feature (eg. `${variable/(.*)/${1:/upcase}/}`) (available to other variables) are available for these Foam-provided variables. See [#693](https://github.com/foambubble/foam/issues/693).
### `FOAM_DATE_*` variables
Foam defines its own set of datetime variables that have a similar behaviour as [VS Code's datetime snippet variables](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables).
For example, `FOAM_DATE_YEAR` has the same behaviour as VS Code's `CURRENT_YEAR`, `FOAM_DATE_SECONDS_UNIX` has the same behaviour as `CURRENT_SECONDS_UNIX`, etc.
By default, prefer using the `FOAM_DATE_` versions. The datetime used to compute the values will be the same for both `FOAM_DATE_` and VS Code's variables, with the exception of the creation notes using the daily note template.
#### Relative daily notes
When referring to daily notes, you can use the relative snippets (`/+1d`, `/tomorrow`, etc.). In these cases, the new notes will be created with the daily note template, but the datetime used should be the relative datetime, not the current datetime.
By using the `FOAM_DATE_` versions of the variables, the correct relative date will populate the variables, instead of the current datetime.
For example, given this daily note template (`.foam/templates/daily-note.md`):
```markdown
# $FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE
## Here's what I'm going to do today
* Thing 1
* Thing 2
```
When the `/tomorrow` snippet is used, `FOAM_DATE_` variables will be populated with tomorrow's date, as expected.
If instead you were to use the VS Code versions of these variables, they would be populated with today's date, not tomorrow's, causing unexpected behaviour.
When creating notes in any other scenario, the `FOAM_DATE_` values are computed using the same datetime as the VS Code ones, so the `FOAM_DATE_` versions can be used in all scenarios by default.
## Metadata

View File

@@ -213,6 +213,11 @@ If that sounds like something you're interested in, I'd love to have you along o
<td align="center"><a href="http://www.prashu.com"><img src="https://avatars.githubusercontent.com/u/476729?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Prashanth Subrahmanyam</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ksprashu" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/JonasSprenger"><img src="https://avatars.githubusercontent.com/u/25108895?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jonas SPRENGER</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=JonasSprenger" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Laptop765"><img src="https://avatars.githubusercontent.com/u/1468359?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Laptop765" title="Documentation">📖</a></td>
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=eltociear" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/memeplex"><img src="https://avatars.githubusercontent.com/u/2845433?v=4?s=60" width="60px;" alt=""/><br /><sub><b>memeplex</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=memeplex" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/AndreiD049"><img src="https://avatars.githubusercontent.com/u/52671223?v=4?s=60" width="60px;" alt=""/><br /><sub><b>AndreiD049</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=AndreiD049" title="Code">💻</a></td>
</tr>
</table>

View File

@@ -34,7 +34,7 @@ Once you've opened/created the Foam repository, it will appear in the `Repositor
## Editing your workspace
When you create or open a page, you can edit the markdown content as usual, as well as [paste images](https://github.com/vsls-contrib/gistpad#pasting-images-1), and create [`[[links]]` to other pages](https://github.com/vsls-contrib/gistpad#links). When you type `[[`, you'll recieve auto-completion for the existing pages in your workspace, and you can also automatically create new pages by simply creating a link to it.
When you create or open a page, you can edit the markdown content as usual, as well as [paste images](https://github.com/vsls-contrib/gistpad#pasting-images-1), and create [`[[links]]` to other pages](https://github.com/vsls-contrib/gistpad#links). When you type `[[`, you'll receive auto-completion for the existing pages in your workspace, and you can also automatically create new pages by simply creating a link to it.
Since you're using the Visual Studio Code markdown editor, you can benefit from all of the rich language services (e.g. syntax highlighting, header collapsing), as well as the extension ecosystem (e.g. [Emojisense](https://marketplace.visualstudio.com/items?itemName=bierner.emojisense)).

View File

@@ -3,7 +3,6 @@
Foam enables you to Link pages together using `[[file-name]]` annotations (i.e. `[[MediaWiki]]` links).
- Type `[[` and start typing a file name for autocompletion.
- Note that your file names should be in `lower-dash-case.md`, and your wikilinks should reference file names exactly: `[[lower-dash-case]]`, not `[[Lower Dash Case]]`.
- See [[link-formatting-and-autocompletion]] for more information, and how to setup your link autocompletions to make this easier.
- `Cmd` + `Click` ( `Ctrl` + `Click` on Windows ) on file name to navigate to file (`F12` also works while your cursor is on the file name)
- `Cmd` + `Click` ( `Ctrl` + `Click` on Windows ) on non-existent file to create that file in the workspace.

View File

@@ -4,5 +4,5 @@
],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.15.1"
"version": "0.15.4"
}

View File

@@ -4,6 +4,33 @@ 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.15.4] - 2021-11-09
Fixes and Improvements:
- Detached Foam URI from VS Code URI. This should improve several path related issues in Windows. Given how core this change is, the release is just about this refactoring to easily detect possible side effects.
## [0.15.3] - 2021-11-08
Fixes and Improvements:
- Avoid delaying decorations on editor switch (#811 - thanks @memeplex)
- Fix preview issue when embedding a note and using reference definitions (#808 - thanks @pderaaij)
## [0.15.2] - 2021-10-27
Features:
- Added `FOAM_DATE_*` template variables (#781)
Fixes and Improvements:
- Dataviz: apply note type color to filter item label
- Dataviz: optimized rendering of graph to reduce load on CPU (#795)
- Preview: improved tag highlight in preview (#785 - thanks @pderaaij)
- Better handling of link reference definition (#786 - thanks @pderaaij)
- Link decorations are now enabled by default (can be turned off in settings)
## [0.15.1] - 2021-10-21
Fixes and Improvements:
@@ -25,7 +52,7 @@ Fixes and Improvements:
## [0.14.2] - 2021-07-24
Features:
Features:
- Autocompletion for tags (#708 - thanks @pderaaij)
- Use templates for new note created from wikilink (#712 - thanks @movermeyer)
@@ -42,7 +69,7 @@ Fixes and Improvements:
## [0.14.0] - 2021-07-13
Features:
Features:
- Create new note from selection (#666 - thanks @pderaaij)
- Use templates for daily notes (#700 - thanks @movermeyer)
@@ -70,9 +97,9 @@ Fixes and Improvements:
- Fixed #667, incorrect resolution of foam-core library
Internal:
Internal:
- BREAKING CHANGE: Removed Foam local plugins
- BREAKING CHANGE: Removed Foam local plugins
If you were previously using the alpha feature of Foam local plugins you will soon be able to migrate the functionality to the V1 API
## [0.13.6] - 2021-06-05

View File

@@ -8,11 +8,11 @@
"type": "git"
},
"homepage": "https://github.com/foambubble/foam",
"version": "0.15.1",
"version": "0.15.4",
"license": "MIT",
"publisher": "foam",
"engines": {
"vscode": "^1.54.0"
"vscode": "^1.47.1"
},
"icon": "icon/FOAM_ICON_256.png",
"categories": [
@@ -361,8 +361,8 @@
"build": "tsc -p ./",
"pretest": "yarn build",
"test": "node ./out/test/run-tests.js",
"unit": "node ./out/test/run-tests.js --unit",
"e2e": "node ./out/test/run-tests.js --e2e",
"test:unit": "node ./out/test/run-tests.js --unit",
"test:e2e": "node ./out/test/run-tests.js --e2e",
"lint": "tsdx lint src",
"clean": "rimraf out",
"watch": "tsc --build ./tsconfig.json --watch",
@@ -390,7 +390,7 @@
"@types/node": "^13.11.0",
"@types/picomatch": "^2.2.1",
"@types/remove-markdown": "^0.1.1",
"@types/vscode": "^1.54.0",
"@types/vscode": "^1.47.1",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
"babel-jest": "^26.2.2",

View File

@@ -36,7 +36,7 @@ describe('generateLinkReferences', () => {
});
it('initialised test graph correctly', () => {
expect(_workspace.list().length).toEqual(6);
expect(_workspace.list().length).toEqual(10);
});
it('should add link references to a file that does not have them', () => {
@@ -105,6 +105,54 @@ describe('generateLinkReferences', () => {
expect(actual).toEqual(expected);
});
it('should put links with spaces in angel brackets', () => {
const note = findBySlug('angel-reference');
const expected = {
newText: textForNote(
note,
`
[//begin]: # "Autogenerated link references for markdown compatibility"
[Note being refered as angel]: <Note being refered as angel> "Note being refered as angel"
[//end]: # "Autogenerated link references"`
),
range: Range.create(3, 0, 3, 0),
};
const actual = generateLinkReferences(note, _workspace, false);
expect(actual!.range.start).toEqual(expected.range.start);
expect(actual!.range.end).toEqual(expected.range.end);
expect(actual!.newText).toEqual(expected.newText);
});
it('should not remove explicitly entered link references', () => {
const note = findBySlug('file-with-explicit-link-references');
const expected = null;
const actual = generateLinkReferences(note, _workspace, false);
expect(actual).toEqual(expected);
});
it('should not remove explicitly entered link references and have an implicit link', () => {
const note = findBySlug('file-with-explicit-and-implicit-link-references');
const expected = {
newText: textForNote(
note,
`[^footerlink]: https://foambubble.github.io/
[linkrefenrece]: https://foambubble.github.io/
[//begin]: # "Autogenerated link references for markdown compatibility"
[first-document]: first-document "First Document"
[//end]: # "Autogenerated link references"`
),
range: Range.create(5, 0, 10, 42),
};
const actual = generateLinkReferences(note, _workspace, false);
expect(actual).toEqual(expected);
});
});
/**

View File

@@ -59,17 +59,78 @@ export const generateLinkReferences = (
} else {
const first = note.definitions[0];
const last = note.definitions[note.definitions.length - 1];
var nonGeneratedReferenceDefinitions = note.definitions;
// if we have more definitions then referenced pages AND the page refers to a page
// we expect non-generated link definitions to be present
// Collect all non-generated definitions, by removing the generated ones
if (
note.definitions.length > markdownReferences.length &&
markdownReferences.length > 0
) {
// remove all autogenerated definitions
const beginIndex = note.definitions.findIndex(
({ label }) => label === '//begin'
);
const endIndex = note.definitions.findIndex(
({ label }) => label === '//end'
);
const generatedDefinitions = [...note.definitions].splice(
beginIndex,
endIndex - beginIndex + 1
);
nonGeneratedReferenceDefinitions = note.definitions.filter(
x => !generatedDefinitions.includes(x)
);
}
// When we only have explicitly defined link definitions &&
// no indication of previously defined generated links &&
// there is no reference to another page, return null
if (
nonGeneratedReferenceDefinitions.length > 0 &&
note.definitions.findIndex(({ label }) => label === '//begin') < 0 &&
markdownReferences.length === 0
) {
return null;
}
// Format link definitions for non-generated links
const nonGeneratedReferences = nonGeneratedReferenceDefinitions
.map(stringifyMarkdownLinkReferenceDefinition)
.join(note.source.eol);
const oldReferences = note.definitions
.map(stringifyMarkdownLinkReferenceDefinition)
.join(note.source.eol);
if (oldReferences === newReferences) {
// When the newly formatted references match the old ones, OR
// when non-generated references are present, but no new ones are generated
// return null
if (
oldReferences === newReferences ||
(nonGeneratedReferenceDefinitions.length > 0 &&
newReferences === '' &&
markdownReferences.length > 0)
) {
return null;
}
var fullReferences = `${newReferences}`;
// If there are any non-generated definitions, add those to the output as well
if (
nonGeneratedReferenceDefinitions.length > 0 &&
markdownReferences.length > 0
) {
fullReferences = `${nonGeneratedReferences}${note.source.eol}${newReferences}`;
}
return {
// @todo: do we need to ensure new lines?
newText: `${newReferences}`,
newText: `${fullReferences}`,
range: Range.createFromPosition(first.range!.start, last.range!.end),
};
}

View File

@@ -441,7 +441,9 @@ function getFoamDefinitions(
export function stringifyMarkdownLinkReferenceDefinition(
definition: NoteLinkDefinition
) {
let text = `[${definition.label}]: ${definition.url}`;
let url =
definition.url.indexOf(' ') > 0 ? `<${definition.url}>` : definition.url;
let text = `[${definition.label}]: ${url}`;
if (definition.title) {
text = `${text} "${definition.title}"`;
}

View File

@@ -4,7 +4,7 @@ import { URI } from './uri';
Logger.setLevel('error');
describe('Foam URIs', () => {
describe('Foam URI', () => {
describe('URI parsing', () => {
const base = URI.file('/path/to/file.md');
test.each([
@@ -24,20 +24,45 @@ describe('Foam URIs', () => {
expect(result.query).toEqual(exp.query);
expect(result.fragment).toEqual(exp.fragment);
});
});
it('supports various cases', () => {
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'
);
it('normalizes the Windows drive letter to upper case', () => {
const upperCase = URI.parse('file:///C:/this/is/a/Path');
const lowerCase = URI.parse('file:///c:/this/is/a/Path');
expect(upperCase.path).toEqual('/C:/this/is/a/Path');
expect(lowerCase.path).toEqual('/C:/this/is/a/Path');
expect(URI.toFsPath(upperCase)).toEqual('C:\\this\\is\\a\\Path');
expect(URI.toFsPath(lowerCase)).toEqual('C:\\this\\is\\a\\Path');
});
it('consistently parses file paths', () => {
const win1 = URI.file('c:\\this\\is\\a\\path');
const win2 = URI.parse('c:\\this\\is\\a\\path');
expect(win1).toEqual(win2);
const unix1 = URI.file('/this/is/a/path');
const unix2 = URI.parse('/this/is/a/path');
expect(unix1).toEqual(unix2);
});
it('correctly parses file paths', () => {
const winUri = URI.file('c:\\this\\is\\a\\path');
const unixUri = URI.file('/this/is/a/path');
expect(winUri).toEqual(
URI.create({
scheme: 'file',
path: '/C:/this/is/a/path',
})
);
expect(unixUri).toEqual(
URI.create({
scheme: 'file',
path: '/this/is/a/path',
})
);
});
});
it('computes a relative uri using a slug', () => {
it('supports computing relative paths', () => {
expect(
URI.computeRelativeURI(URI.file('/my/file.md'), '../hello.md')
).toEqual(URI.file('/hello.md'));
@@ -48,4 +73,16 @@ describe('Foam URIs', () => {
URI.computeRelativeURI(URI.file('/my/file.markdown'), '../hello')
).toEqual(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

@@ -6,7 +6,6 @@
import * as paths from 'path';
import { CharCode } from '../common/charCode';
import { isWindows } from '../common/platform';
/**
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
@@ -39,6 +38,9 @@ const _regexp = /^(([^:/?#]{2,}?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
export abstract class URI {
static create(from: Partial<URI>): URI {
// When using this method we assume the path is already posix
// so we don't check whether it's a Windows path, nor we do any
// conversion
return {
scheme: from.scheme ?? _empty,
authority: from.authority ?? _empty,
@@ -53,10 +55,14 @@ export abstract class URI {
if (!match) {
return URI.create({});
}
let path = percentDecode(match[5] ?? _empty);
if (URI.isWindowsPath(path)) {
path = windowsPathToUriPath(path);
}
return URI.create({
scheme: match[2] || 'file',
authority: percentDecode(match[4] ?? _empty),
path: percentDecode(match[5] ?? _empty),
path: path,
query: percentDecode(match[7] ?? _empty),
fragment: percentDecode(match[9] ?? _empty),
});
@@ -104,12 +110,8 @@ export abstract class URI {
// normalize to fwd-slashes on windows,
// on other systems bwd-slashes are valid
// filename character, eg /f\oo/ba\r.txt
if (isWindows) {
if (path.startsWith(_slash)) {
path = `${path.replace(/\\/g, _slash)}`;
} else {
path = `/${path.replace(/\\/g, _slash)}`;
}
if (URI.isWindowsPath(path)) {
path = windowsPathToUriPath(path);
}
// check for authority as used in UNC shares
@@ -185,7 +187,7 @@ export abstract class URI {
throw new Error(`[UriError]: cannot call joinPath on URI without path`);
}
let newPath: string;
if (isWindows && uri.scheme === 'file') {
if (URI.isWindowsPath(uri.path) && uri.scheme === 'file') {
newPath = URI.file(paths.win32.join(URI.toFsPath(uri), ...pathFragment))
.path;
} else {
@@ -194,7 +196,7 @@ export abstract class URI {
return URI.create({ ...uri, path: newPath });
}
static toFsPath(uri: URI, keepDriveLetterCasing = true): string {
static toFsPath(uri: URI): string {
let value: string;
if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') {
// unc path: file://shares/c$/far/boo
@@ -207,17 +209,13 @@ export abstract class URI {
uri.path.charCodeAt(1) <= CharCode.z)) &&
uri.path.charCodeAt(2) === CharCode.Colon
) {
if (!keepDriveLetterCasing) {
// windows drive letter: file:///c:/far/boo
value = uri.path[1].toLowerCase() + uri.path.substr(2);
} else {
value = uri.path.substr(1);
}
// windows drive letter: file:///C:/far/boo
value = uri.path[1].toUpperCase() + uri.path.substr(2);
} else {
// other path
value = uri.path;
}
if (isWindows) {
if (URI.isWindowsPath(value)) {
value = value.replace(/\//g, '\\');
}
return value;
@@ -229,6 +227,15 @@ export abstract class URI {
// --- utility
static isWindowsPath(path: string) {
return (
(path.length >= 2 && path.charCodeAt(1) === CharCode.Colon) ||
(path.length >= 3 &&
path.charCodeAt(0) === CharCode.Slash &&
path.charCodeAt(2) === CharCode.Colon)
);
}
static isUri(thing: any): thing is URI {
if (!thing) {
return false;
@@ -285,6 +292,33 @@ function percentDecode(str: string): string {
);
}
/**
* Converts a windows-like path to standard URI path
* - Normalize the Windows drive letter to upper case
* - replace \ with /
* - always start with /
*
* see https://github.com/foambubble/foam/issues/813
* see https://github.com/microsoft/vscode/issues/43959
* see https://github.com/microsoft/vscode/issues/116298
*
* @param path the path to convert
* @returns the URI compatible path
*/
function windowsPathToUriPath(path: string): string {
path = path.charCodeAt(0) === CharCode.Slash ? path : `/${path}`;
path = path.replace(/\\/g, _slash);
const code = path.charCodeAt(1);
if (
path.charCodeAt(2) === CharCode.Colon &&
code >= CharCode.a &&
code <= CharCode.z
) {
path = `/${String.fromCharCode(code - 32)}:${path.substr(3)}`; // "/C:".length === 3
}
return path;
}
/**
* Create the external version of a uri
*/
@@ -331,20 +365,20 @@ function encode(uri: URI, skipEncoding: boolean): string {
}
}
if (path) {
// lower-case windows drive letters in /C:/fff or C:/fff
// upper-case windows drive letters in /c:/fff or c:/fff
if (
path.length >= 3 &&
path.charCodeAt(0) === CharCode.Slash &&
path.charCodeAt(2) === CharCode.Colon
) {
const code = path.charCodeAt(1);
if (code >= CharCode.A && code <= CharCode.Z) {
path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3
if (code >= CharCode.a && code <= CharCode.z) {
path = `/${String.fromCharCode(code - 32)}:${path.substr(3)}`; // "/C:".length === 3
}
} else if (path.length >= 2 && path.charCodeAt(1) === CharCode.Colon) {
const code = path.charCodeAt(0);
if (code >= CharCode.A && code <= CharCode.Z) {
path = `${String.fromCharCode(code + 32)}:${path.substr(2)}`; // "/c:".length === 3
if (code >= CharCode.a && code <= CharCode.z) {
path = `${String.fromCharCode(code - 32)}:${path.substr(2)}`; // "/C:".length === 3
}
}
// encode the rest of the path

View File

@@ -1,7 +1,14 @@
import { workspace } from 'vscode';
import { getDailyNotePath } from './dated-notes';
import { createDailyNoteIfNotExists, getDailyNotePath } from './dated-notes';
import { URI } from './core/model/uri';
import { isWindows } from './utils';
import {
cleanWorkspace,
closeEditors,
createFile,
showInEditor,
} from './test/test-utils-vscode';
import { fromVsCodeUri } from './utils/vsc-utils';
describe('getDailyNotePath', () => {
const date = new Date('2021-02-07T00:00:00Z');
@@ -14,11 +21,14 @@ describe('getDailyNotePath', () => {
const config = 'journal';
const expectedPath = URI.joinPath(
workspace.workspaceFolders[0].uri,
fromVsCodeUri(workspace.workspaceFolders[0].uri),
config,
`${isoDate}.md`
);
const oldValue = await workspace
.getConfiguration('foam')
.get('openDailyNote.directory');
await workspace
.getConfiguration('foam')
.update('openDailyNote.directory', config);
@@ -27,16 +37,24 @@ describe('getDailyNotePath', () => {
expect(URI.toFsPath(getDailyNotePath(foamConfiguration, date))).toEqual(
URI.toFsPath(expectedPath)
);
await workspace
.getConfiguration('foam')
.update('openDailyNote.directory', oldValue);
});
test('Uses absolute directories without modification', async () => {
const config = isWindows
? 'c:\\absolute_path\\journal'
? 'C:\\absolute_path\\journal'
: '/absolute_path/journal';
const expectedPath = isWindows
? `${config}\\${isoDate}.md`
: `${config}/${isoDate}.md`;
const oldValue = await workspace
.getConfiguration('foam')
.get('openDailyNote.directory');
await workspace
.getConfiguration('foam')
.update('openDailyNote.directory', config);
@@ -45,5 +63,36 @@ describe('getDailyNotePath', () => {
expect(URI.toFsPath(getDailyNotePath(foamConfiguration, date))).toMatch(
expectedPath
);
await workspace
.getConfiguration('foam')
.update('openDailyNote.directory', oldValue);
});
});
describe('Daily note template', () => {
it('Uses the daily note variables in the template', async () => {
const targetDate = new Date(2021, 8, 12);
// eslint-disable-next-line no-template-curly-in-string
await createFile('hello ${FOAM_DATE_MONTH_NAME} ${FOAM_DATE_DATE} hello', [
'.foam',
'templates',
'daily-note.md',
]);
const config = workspace.getConfiguration('foam');
const uri = getDailyNotePath(config, targetDate);
await createDailyNoteIfNotExists(config, uri, targetDate);
const doc = await showInEditor(uri);
const content = doc.editor.document.getText();
expect(content).toEqual('hello September 12 hello');
});
afterAll(async () => {
await cleanWorkspace();
await closeEditors();
});
});

View File

@@ -4,6 +4,7 @@ import { isAbsolute } from 'path';
import { focusNote, pathExists } from './utils';
import { URI } from './core/model/uri';
import { createNoteFromDailyNoteTemplate } from './features/create-from-template';
import { fromVsCodeUri } from './utils/vsc-utils';
/**
* Open the daily note file.
@@ -13,7 +14,7 @@ import { createNoteFromDailyNoteTemplate } from './features/create-from-template
*
* @param date A given date to be formatted as filename.
*/
async function openDailyNoteFor(date?: Date) {
export async function openDailyNoteFor(date?: Date) {
const foamConfiguration = workspace.getConfiguration('foam');
const currentDate = date !== undefined ? date : new Date();
@@ -40,7 +41,7 @@ async function openDailyNoteFor(date?: Date) {
* @param date A given date to be formatted as filename.
* @returns The path to the daily note file.
*/
function getDailyNotePath(
export function getDailyNotePath(
configuration: WorkspaceConfiguration,
date: Date
): URI {
@@ -52,7 +53,7 @@ function getDailyNotePath(
return URI.joinPath(URI.file(dailyNoteDirectory), dailyNoteFilename);
} else {
return URI.joinPath(
workspace.workspaceFolders[0].uri,
fromVsCodeUri(workspace.workspaceFolders[0].uri),
dailyNoteDirectory,
dailyNoteFilename
);
@@ -70,7 +71,7 @@ function getDailyNotePath(
* @param date A given date to be formatted as filename.
* @returns The daily note's filename.
*/
function getDailyNoteFileName(
export function getDailyNoteFileName(
configuration: WorkspaceConfiguration,
date: Date
): string {
@@ -95,10 +96,10 @@ function getDailyNoteFileName(
* @param currentDate The current date, to be used as a title.
* @returns Wether the file was created.
*/
async function createDailyNoteIfNotExists(
export async function createDailyNoteIfNotExists(
configuration: WorkspaceConfiguration,
dailyNotePath: URI,
currentDate: Date
targetDate: Date
) {
if (await pathExists(dailyNotePath)) {
return false;
@@ -113,17 +114,14 @@ foam_template:
name: New Daily Note
description: Foam's default daily note template
---
# ${dateFormat(currentDate, titleFormat, false)}
# ${dateFormat(targetDate, titleFormat, false)}
`;
await createNoteFromDailyNoteTemplate(dailyNotePath, templateFallbackText);
await createNoteFromDailyNoteTemplate(
dailyNotePath,
templateFallbackText,
targetDate
);
return true;
}
export {
openDailyNoteFor,
getDailyNoteFileName,
createDailyNoteIfNotExists,
getDailyNotePath,
};

View File

@@ -8,6 +8,7 @@ import { Logger } from './core/utils/log';
import { features } from './features';
import { getConfigFromVscode } from './services/config';
import { VsCodeOutputLogger, exposeLogger } from './services/logging';
import { fromVsCodeUri } from './utils/vsc-utils';
function createMarkdownProvider(config: FoamConfig): MarkdownResourceProvider {
const matcher = new Matcher(
@@ -18,9 +19,9 @@ function createMarkdownProvider(config: FoamConfig): MarkdownResourceProvider {
const provider = new MarkdownResourceProvider(matcher, triggers => {
const watcher = workspace.createFileSystemWatcher('**/*');
return [
watcher.onDidChange(triggers.onDidChange),
watcher.onDidCreate(triggers.onDidCreate),
watcher.onDidDelete(triggers.onDidDelete),
watcher.onDidChange(uri => triggers.onDidChange(fromVsCodeUri(uri))),
watcher.onDidCreate(uri => triggers.onDidCreate(fromVsCodeUri(uri))),
watcher.onDidDelete(uri => triggers.onDidDelete(fromVsCodeUri(uri))),
watcher,
];
});

View File

@@ -8,7 +8,7 @@ import {
import { BacklinksTreeDataProvider, BacklinkTreeItem } from './backlinks';
import { ResourceTreeItem } from '../utils/grouped-resources-tree-data-provider';
import { OPEN_COMMAND } from './utility-commands';
import { toVsCodeUri } from '../utils/vsc-utils';
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
import { FoamGraph } from '../core/model/graph';
import { URI } from '../core/model/uri';
@@ -25,7 +25,7 @@ describe('Backlinks panel', () => {
await cleanWorkspace();
});
const rootUri = workspace.workspaceFolders[0].uri;
const rootUri = fromVsCodeUri(workspace.workspaceFolders[0].uri);
const ws = createTestWorkspace();
const noteA = createTestNote({

View File

@@ -10,6 +10,7 @@ import { FoamWorkspace } from '../core/model/workspace';
import { FoamGraph } from '../core/model/graph';
import { Resource, ResourceLink } from '../core/model/note';
import { Range } from '../core/model/range';
import { fromVsCodeUri } from '../utils/vsc-utils';
const feature: FoamFeature = {
activate: async (
@@ -21,7 +22,9 @@ const feature: FoamFeature = {
const provider = new BacklinksTreeDataProvider(foam.workspace, foam.graph);
vscode.window.onDidChangeActiveTextEditor(async () => {
provider.target = vscode.window.activeTextEditor?.document.uri;
provider.target = vscode.window.activeTextEditor
? fromVsCodeUri(vscode.window.activeTextEditor?.document.uri)
: undefined;
await provider.refresh();
});

View File

@@ -8,6 +8,7 @@ import {
import path from 'path';
import { isWindows } from '../utils';
import { URI } from '../core/model/uri';
import { fromVsCodeUri } from '../utils/vsc-utils';
describe('substituteFoamVariables', () => {
test('Does nothing if no Foam-specific variables are used', () => {
@@ -91,6 +92,83 @@ describe('resolveFoamVariables', () => {
expected
);
});
test('Resolves FOAM_DATE_* properties with current day by default', async () => {
const variables = [
'FOAM_DATE_YEAR',
'FOAM_DATE_YEAR_SHORT',
'FOAM_DATE_MONTH',
'FOAM_DATE_MONTH_NAME',
'FOAM_DATE_MONTH_NAME_SHORT',
'FOAM_DATE_DATE',
'FOAM_DATE_DAY_NAME',
'FOAM_DATE_DAY_NAME_SHORT',
'FOAM_DATE_HOUR',
'FOAM_DATE_MINUTE',
'FOAM_DATE_SECOND',
'FOAM_DATE_SECONDS_UNIX',
];
const expected = new Map<string, string>();
expected.set(
'FOAM_DATE_YEAR',
new Date().toLocaleString('default', { year: 'numeric' })
);
expected.set(
'FOAM_DATE_MONTH_NAME',
new Date().toLocaleString('default', { month: 'long' })
);
expected.set(
'FOAM_DATE_DATE',
new Date().toLocaleString('default', { day: '2-digit' })
);
const givenValues = new Map<string, string>();
expect(await resolveFoamVariables(variables, givenValues)).toEqual(
expect.objectContaining(expected)
);
});
test('Resolves FOAM_DATE_* properties with given date', async () => {
const targetDate = new Date(2021, 9, 12, 1, 2, 3);
const variables = [
'FOAM_DATE_YEAR',
'FOAM_DATE_YEAR_SHORT',
'FOAM_DATE_MONTH',
'FOAM_DATE_MONTH_NAME',
'FOAM_DATE_MONTH_NAME_SHORT',
'FOAM_DATE_DATE',
'FOAM_DATE_DAY_NAME',
'FOAM_DATE_DAY_NAME_SHORT',
'FOAM_DATE_HOUR',
'FOAM_DATE_MINUTE',
'FOAM_DATE_SECOND',
'FOAM_DATE_SECONDS_UNIX',
];
const expected = new Map<string, string>();
expected.set('FOAM_DATE_YEAR', '2021');
expected.set('FOAM_DATE_YEAR_SHORT', '21');
expected.set('FOAM_DATE_MONTH', '10');
expected.set('FOAM_DATE_MONTH_NAME', 'October');
expected.set('FOAM_DATE_MONTH_NAME_SHORT', 'Oct');
expected.set('FOAM_DATE_DATE', '12');
expected.set('FOAM_DATE_DAY_NAME', 'Tuesday');
expected.set('FOAM_DATE_DAY_NAME_SHORT', 'Tue');
expected.set('FOAM_DATE_HOUR', '01');
expected.set('FOAM_DATE_MINUTE', '02');
expected.set('FOAM_DATE_SECOND', '03');
expected.set(
'FOAM_DATE_SECONDS_UNIX',
(targetDate.getTime() / 1000).toString()
);
const givenValues = new Map<string, string>();
expect(
await resolveFoamVariables(variables, givenValues, targetDate)
).toEqual(expected);
});
});
describe('resolveFoamTemplateVariables', () => {
@@ -266,7 +344,7 @@ describe('resolveFoamTemplateVariables', () => {
describe('determineDefaultFilepath', () => {
test('Absolute filepath metadata is unchanged', () => {
const absolutePath = isWindows
? 'c:\\absolute_path\\journal\\My Note Title.md'
? 'C:\\absolute_path\\journal\\My Note Title.md'
: '/absolute_path/journal/My Note Title.md';
const resolvedValues = new Map<string, string>();
@@ -296,7 +374,7 @@ describe('determineDefaultFilepath', () => {
);
const expectedPath = path.join(
workspace.workspaceFolders[0].uri.fsPath,
URI.toFsPath(fromVsCodeUri(workspace.workspaceFolders[0].uri)),
relativePath
);

View File

@@ -28,21 +28,12 @@ describe('createFromTemplate', () => {
jest.clearAllMocks();
});
it.skip('can be cancelled while resolving FOAM_TITLE', async () => {
it('can be cancelled while resolving FOAM_TITLE', async () => {
const spy = jest
.spyOn(window, 'showInputBox')
.mockImplementation(jest.fn(() => Promise.resolve(undefined)));
// The following is a workaround code to achieve this:
// const fileWriteSpy = jest.spyOn(workspace.fs, 'writeFile');
// as writeFile is a read-only property.
// the approach is a bit risky and britte as it overwrites the whole module
// but will be enough for now.
const oldFs = workspace.fs;
const fileWriteSpy = jest.fn(workspace.fs.writeFile);
Object.defineProperty(workspace, 'fs', {
value: { writeFile: fileWriteSpy },
});
const fileWriteSpy = jest.spyOn(workspace.fs, 'writeFile');
await commands.executeCommand(
'foam-vscode.create-note-from-default-template'
@@ -55,11 +46,6 @@ describe('createFromTemplate', () => {
});
expect(fileWriteSpy).toHaveBeenCalledTimes(0);
// restore the old fs object or all tests will be affected by this one
Object.defineProperty(workspace, 'fs', {
value: oldFs,
});
});
});

View File

@@ -17,11 +17,11 @@ import {
} from 'vscode';
import { FoamFeature } from '../types';
import { focusNote } from '../utils';
import { toVsCodeUri } from '../utils/vsc-utils';
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
import { extractFoamTemplateFrontmatterMetadata } from '../utils/template-frontmatter-parser';
const templatesDir = URI.joinPath(
workspace.workspaceFolders[0].uri,
fromVsCodeUri(workspace.workspaceFolders[0].uri),
'.foam',
'templates'
);
@@ -41,7 +41,22 @@ interface FoamSelectionContent {
content: string;
}
const knownFoamVariables = new Set(['FOAM_TITLE', 'FOAM_SELECTED_TEXT']);
const knownFoamVariables = new Set([
'FOAM_TITLE',
'FOAM_SELECTED_TEXT',
'FOAM_DATE_YEAR',
'FOAM_DATE_YEAR_SHORT',
'FOAM_DATE_MONTH',
'FOAM_DATE_MONTH_NAME',
'FOAM_DATE_MONTH_NAME_SHORT',
'FOAM_DATE_DATE',
'FOAM_DATE_DAY_NAME',
'FOAM_DATE_DAY_NAME_SHORT',
'FOAM_DATE_HOUR',
'FOAM_DATE_MINUTE',
'FOAM_DATE_SECOND',
'FOAM_DATE_SECONDS_UNIX',
]);
const wikilinkDefaultTemplateText = `# $\{1:$FOAM_TITLE}\n\n$0`;
const defaultTemplateDefaultText: string = `---
@@ -86,7 +101,9 @@ async function templateMetadata(
}
async function getTemplates(): Promise<URI[]> {
const templates = await workspace.findFiles('.foam/templates/**.md', null);
const templates = await workspace
.findFiles('.foam/templates/**.md', null)
.then(v => v.map(uri => fromVsCodeUri(uri)));
return templates;
}
@@ -133,9 +150,14 @@ function resolveFoamSelectedText() {
class Resolver {
promises = new Map<string, Thenable<string>>();
resolve(name: string, givenValues: Map<string, string>): Thenable<string> {
if (givenValues.has(name)) {
this.promises.set(name, Promise.resolve(givenValues.get(name)));
constructor(
private givenValues: Map<string, string>,
private foamDate: Date
) {}
resolve(name: string): Thenable<string> {
if (this.givenValues.has(name)) {
this.promises.set(name, Promise.resolve(this.givenValues.get(name)));
} else if (!this.promises.has(name)) {
switch (name) {
case 'FOAM_TITLE':
@@ -144,6 +166,117 @@ class Resolver {
case 'FOAM_SELECTED_TEXT':
this.promises.set(name, Promise.resolve(resolveFoamSelectedText()));
break;
case 'FOAM_DATE_YEAR':
this.promises.set(
name,
Promise.resolve(
this.foamDate.toLocaleString('default', { year: 'numeric' })
)
);
break;
case 'FOAM_DATE_YEAR_SHORT':
this.promises.set(
name,
Promise.resolve(
this.foamDate.toLocaleString('default', { year: '2-digit' })
)
);
break;
case 'FOAM_DATE_MONTH':
this.promises.set(
name,
Promise.resolve(
this.foamDate.toLocaleString('default', { month: '2-digit' })
)
);
break;
case 'FOAM_DATE_MONTH_NAME':
this.promises.set(
name,
Promise.resolve(
this.foamDate.toLocaleString('default', { month: 'long' })
)
);
break;
case 'FOAM_DATE_MONTH_NAME_SHORT':
this.promises.set(
name,
Promise.resolve(
this.foamDate.toLocaleString('default', { month: 'short' })
)
);
break;
case 'FOAM_DATE_DATE':
this.promises.set(
name,
Promise.resolve(
this.foamDate.toLocaleString('default', { day: '2-digit' })
)
);
break;
case 'FOAM_DATE_DAY_NAME':
this.promises.set(
name,
Promise.resolve(
this.foamDate.toLocaleString('default', { weekday: 'long' })
)
);
break;
case 'FOAM_DATE_DAY_NAME_SHORT':
this.promises.set(
name,
Promise.resolve(
this.foamDate.toLocaleString('default', { weekday: 'short' })
)
);
break;
case 'FOAM_DATE_HOUR':
this.promises.set(
name,
Promise.resolve(
this.foamDate
.toLocaleString('default', {
hour: '2-digit',
hour12: false,
})
.padStart(2, '0')
)
);
break;
case 'FOAM_DATE_MINUTE':
this.promises.set(
name,
Promise.resolve(
this.foamDate
.toLocaleString('default', {
minute: '2-digit',
hour12: false,
})
.padStart(2, '0')
)
);
break;
case 'FOAM_DATE_SECOND':
this.promises.set(
name,
Promise.resolve(
this.foamDate
.toLocaleString('default', {
second: '2-digit',
hour12: false,
})
.padStart(2, '0')
)
);
break;
case 'FOAM_DATE_SECONDS_UNIX':
this.promises.set(
name,
Promise.resolve(
(this.foamDate.getTime() / 1000).toString().padStart(2, '0')
)
);
break;
default:
this.promises.set(name, Promise.resolve(name));
break;
@@ -156,11 +289,12 @@ class Resolver {
export async function resolveFoamVariables(
variables: string[],
givenValues: Map<string, string>
givenValues: Map<string, string>,
foamDate: Date = new Date()
) {
const resolver = new Resolver();
const resolver = new Resolver(givenValues, foamDate);
const promises = variables.map(async variable =>
Promise.resolve([variable, await resolver.resolve(variable, givenValues)])
Promise.resolve([variable, await resolver.resolve(variable)])
);
const results = await Promise.all(promises);
@@ -293,13 +427,18 @@ function appendSnippetVariableUsage(templateText: string, variable: string) {
export async function resolveFoamTemplateVariables(
templateText: string,
extraVariablesToResolve: Set<string> = new Set(),
givenValues: Map<string, string> = new Map()
givenValues: Map<string, string> = new Map(),
foamDate: Date = new Date()
): Promise<[Map<string, string>, string]> {
const variablesInTemplate = findFoamVariables(templateText.toString());
const variables = variablesInTemplate.concat(...extraVariablesToResolve);
const uniqVariables = [...new Set(variables)];
const resolvedValues = await resolveFoamVariables(uniqVariables, givenValues);
const resolvedValues = await resolveFoamVariables(
uniqVariables,
givenValues,
foamDate
);
if (
resolvedValues.get('FOAM_SELECTED_TEXT') &&
@@ -340,7 +479,7 @@ function currentDirectoryFilepath(filename: string) {
const currentDir =
activeFile !== undefined
? URI.parse(path.dirname(activeFile))
: workspace.workspaceFolders[0].uri;
: fromVsCodeUri(workspace.workspaceFolders[0].uri);
return URI.joinPath(currentDir, filename);
}
@@ -381,7 +520,7 @@ async function replaceSelectionWithWikiLink(
function resolveFilepathAttribute(filepath) {
return isAbsolute(filepath)
? URI.file(filepath)
: URI.joinPath(workspace.workspaceFolders[0].uri, filepath);
: URI.joinPath(fromVsCodeUri(workspace.workspaceFolders[0].uri), filepath);
}
export function determineDefaultFilepath(
@@ -410,14 +549,16 @@ export function determineDefaultFilepath(
*/
export async function createNoteFromDailyNoteTemplate(
filepathFallbackURI: URI,
templateFallbackText: string
templateFallbackText: string,
targetDate: Date
): Promise<void> {
return await createNoteFromDefaultTemplate(
new Map(),
new Set(['FOAM_SELECTED_TEXT']),
dailyNoteTemplateUri,
filepathFallbackURI,
templateFallbackText
templateFallbackText,
targetDate
);
}
@@ -455,7 +596,8 @@ async function createNoteFromDefaultTemplate(
]),
templateUri: URI = defaultTemplateUri,
filepathFallbackURI: URI = undefined,
templateFallbackText: string = defaultTemplateDefaultText
templateFallbackText: string = defaultTemplateDefaultText,
foamDate: Date = new Date()
): Promise<void> {
const templateText = existsSync(URI.toFsPath(templateUri))
? await workspace.fs
@@ -474,7 +616,8 @@ async function createNoteFromDefaultTemplate(
] = await resolveFoamTemplateVariables(
templateText,
extraVariablesToResolve,
givenValues.set('FOAM_SELECTED_TEXT', selectedContent?.content ?? '')
givenValues.set('FOAM_SELECTED_TEXT', selectedContent?.content ?? ''),
foamDate
);
} catch (err) {
if (err instanceof UserCancelledOperation) {

View File

@@ -7,6 +7,7 @@ import { getGraphStyle, getTitleMaxLength } from '../settings';
import { isSome } from '../utils';
import { Foam } from '../core/model/foam';
import { Logger } from '../core/utils/log';
import { fromVsCodeUri } from '../utils/vsc-utils';
const feature: FoamFeature = {
activate: (context: vscode.ExtensionContext, foamPromise: Promise<Foam>) => {
@@ -46,7 +47,7 @@ const feature: FoamFeature = {
vscode.window.onDidChangeActiveTextEditor(e => {
if (e?.document?.uri?.scheme === 'file') {
const note = foam.workspace.get(e.document.uri);
const note = foam.workspace.get(fromVsCodeUri(e.document.uri));
if (isSome(note)) {
panel.webview.postMessage({
type: 'didSelectNote',
@@ -143,7 +144,7 @@ async function createGraphPanel(foam: Foam, context: vscode.ExtensionContext) {
case 'webviewDidSelectNode':
const noteUri = vscode.Uri.parse(message.payload);
const selectedNote = foam.workspace.get(noteUri);
const selectedNote = foam.workspace.get(fromVsCodeUri(noteUri));
if (isSome(selectedNote)) {
const doc = await vscode.workspace.openTextDocument(

View File

@@ -9,6 +9,7 @@ import {
import { ResourceParser } from '../core/model/note';
import { FoamWorkspace } from '../core/model/workspace';
import { Foam } from '../core/model/foam';
import { fromVsCodeUri } from '../utils/vsc-utils';
export const CONFIG_KEY = 'decorations.links.enable';
@@ -34,7 +35,10 @@ const updateDecorations = (
if (!editor || !areDecorationsEnabled()) {
return;
}
const note = parser.parse(editor.document.uri, editor.document.getText());
const note = parser.parse(
fromVsCodeUri(editor.document.uri),
editor.document.getText()
);
let linkRanges = [];
let placeholderRanges = [];
note.links.forEach(link => {
@@ -60,16 +64,18 @@ const feature: FoamFeature = {
const foam = await foamPromise;
let activeEditor = vscode.window.activeTextEditor;
const immediatelyUpdateDecorations = updateDecorations(
areDecorationsEnabled,
foam.services.parser,
foam.workspace
);
const debouncedUpdateDecorations = debounce(
updateDecorations(
areDecorationsEnabled,
foam.services.parser,
foam.workspace
),
immediatelyUpdateDecorations,
500
);
debouncedUpdateDecorations(activeEditor);
immediatelyUpdateDecorations(activeEditor);
context.subscriptions.push(
areDecorationsEnabled,
@@ -77,7 +83,7 @@ const feature: FoamFeature = {
placeholderDecoration,
vscode.window.onDidChangeActiveTextEditor(editor => {
activeEditor = editor;
debouncedUpdateDecorations(activeEditor);
immediatelyUpdateDecorations(activeEditor);
}),
vscode.workspace.onDidChangeTextDocument(event => {
if (activeEditor && event.document === activeEditor.document) {

View File

@@ -32,7 +32,7 @@ describe('Document links provider', () => {
const { uri, content } = await createFile('');
const ws = new FoamWorkspace().set(parser.parse(uri, content));
const doc = await vscode.workspace.openTextDocument(uri);
const doc = await vscode.workspace.openTextDocument(toVsCodeUri(uri));
const provider = new LinkProvider(ws, parser);
const links = provider.provideDocumentLinks(doc);
@@ -45,7 +45,7 @@ describe('Document links provider', () => {
);
const ws = new FoamWorkspace().set(parser.parse(uri, content));
const doc = await vscode.workspace.openTextDocument(uri);
const doc = await vscode.workspace.openTextDocument(toVsCodeUri(uri));
const provider = new LinkProvider(ws, parser);
const links = provider.provideDocumentLinks(doc);
@@ -98,7 +98,7 @@ describe('Document links provider', () => {
expect(links.length).toEqual(1);
expect(links[0].target).toEqual(
OPEN_COMMAND.asURI(toVsCodeUri(URI.placeholder('a placeholder')))
OPEN_COMMAND.asURI(URI.placeholder('a placeholder'))
);
expect(links[0].range).toEqual(new vscode.Range(0, 18, 0, 35));
});

View File

@@ -3,7 +3,7 @@ import { URI } from '../core/model/uri';
import { FoamFeature } from '../types';
import { mdDocSelector } from '../utils';
import { OPEN_COMMAND } from './utility-commands';
import { toVsCodeRange, toVsCodeUri } from '../utils/vsc-utils';
import { fromVsCodeUri, toVsCodeRange, toVsCodeUri } from '../utils/vsc-utils';
import { getFoamVsCodeConfig } from '../services/config';
import { Foam } from '../core/model/foam';
import { FoamWorkspace } from '../core/model/workspace';
@@ -38,11 +38,14 @@ export class LinkProvider implements vscode.DocumentLinkProvider {
public provideDocumentLinks(
document: vscode.TextDocument
): vscode.DocumentLink[] {
const resource = this.parser.parse(document.uri, document.getText());
const resource = this.parser.parse(
fromVsCodeUri(document.uri),
document.getText()
);
return resource.links.map(link => {
const target = this.workspace.resolveLink(resource, link);
const command = OPEN_COMMAND.asURI(toVsCodeUri(target));
const command = OPEN_COMMAND.asURI(target);
const documentLink = new vscode.DocumentLink(
toVsCodeRange(link.range),
command

View File

@@ -4,27 +4,36 @@ import {
MarkdownResourceProvider,
} from '../core/markdown-provider';
import { FoamGraph } from '../core/model/graph';
import { URI } from '../core/model/uri';
import { FoamWorkspace } from '../core/model/workspace';
import { Matcher } from '../core/services/datastore';
import { getConfigFromVscode } from '../services/config';
import {
cleanWorkspace,
closeEditors,
createFile,
showInEditor,
} from '../test/test-utils-vscode';
import { toVsCodeUri } from '../utils/vsc-utils';
import { HoverProvider } from './hover-provider';
// We can't use createTestWorkspace from /packages/foam-vscode/src/test/test-utils.ts
// because we need a fully instantiated MarkdownResourceProvider (with a real instance of ResourceParser).
// because we need a MarkdownResourceProvider with a real instance of FileDataStore.
const createWorkspace = () => {
const matcher = new Matcher([URI.file('/')], ['**/*']);
const config = getConfigFromVscode();
const matcher = new Matcher(
config.workspaceFolders,
config.includeGlobs,
config.ignoreGlobs
);
const resourceProvider = new MarkdownResourceProvider(matcher);
const workspace = new FoamWorkspace();
workspace.registerProvider(resourceProvider);
return workspace;
};
const getValue = (value: vscode.MarkdownString | vscode.MarkedString) =>
value instanceof vscode.MarkdownString ? value.value : value;
describe('Hover provider', () => {
const noCancelToken: vscode.CancellationToken = {
isCancellationRequested: false,
@@ -33,36 +42,6 @@ describe('Hover provider', () => {
const parser = createMarkdownParser([]);
const hoverEnabled = () => true;
const fileBContent = `# File B Title
---
tags: my-tag1 my-tag2
---
The content of file B
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`;
// Fixture needed as long tests are running with vscode 1.53.0 (MarkdownString is not available)
const simpleTooltipExpectedFormat =
'File B Title --- tags: my-tag1 my-tag2 --- The content of file B aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ' +
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccccccccccccccccccc dddddddddddd...';
// Fixture to use when tests are running with vscode version >= STABLE_MARKDOWN_STRING_API_VERSION (1.52.1)
/*const markdownTooltipExpectedFormat = `# File B Title
---
tags: my-tag1 my-tag2
---
The content of file B
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`;*/
beforeAll(async () => {
await cleanWorkspace();
});
@@ -82,7 +61,7 @@ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`;*/
const graph = FoamGraph.fromWorkspace(ws);
const provider = new HoverProvider(hoverEnabled, ws, graph, parser);
const doc = await vscode.workspace.openTextDocument(uri);
const doc = await vscode.workspace.openTextDocument(toVsCodeUri(uri));
const pos = new vscode.Position(0, 0);
const result = await provider.provideHover(doc, pos, noCancelToken);
@@ -100,7 +79,7 @@ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`;*/
const provider = new HoverProvider(hoverEnabled, ws, graph, parser);
const doc = await vscode.workspace.openTextDocument(uri);
const doc = await vscode.workspace.openTextDocument(toVsCodeUri(uri));
const pos = new vscode.Position(0, 0);
const result = await provider.provideHover(doc, pos, noCancelToken);
@@ -200,8 +179,7 @@ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`;*/
const result = await provider.provideHover(doc, pos, noCancelToken);
expect(result.contents).toHaveLength(2);
expect(result.contents[0]).toHaveProperty(
'value',
expect(getValue(result.contents[0])).toEqual(
`This is some content from file B`
);
ws.dispose();
@@ -227,8 +205,7 @@ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`;*/
const result = await provider.provideHover(doc, pos, noCancelToken);
expect(result.contents).toHaveLength(2);
expect(result.contents[0]).toHaveProperty(
'value',
expect(getValue(result.contents[0])).toEqual(
`This is some content from file B`
);
ws.dispose();
@@ -258,10 +235,7 @@ The content of file B`);
const result = await provider.provideHover(doc, pos, noCancelToken);
expect(result.contents).toHaveLength(2);
expect(result.contents[0]).toHaveProperty(
'value',
`The content of file B`
);
expect(getValue(result.contents[0])).toEqual(`The content of file B`);
ws.dispose();
graph.dispose();
});
@@ -309,11 +283,8 @@ The content of file B`);
const result = await provider.provideHover(doc, pos, noCancelToken);
expect(result.contents).toHaveLength(2);
expect(result.contents[0]).toHaveProperty(
'value',
`This is some content`
);
expect((result.contents[1] as any).value).toMatch(
expect(getValue(result.contents[0])).toEqual(`This is some content`);
expect(getValue(result.contents[1])).toMatch(
/^Also referenced in 1 note:/
);
ws.dispose();
@@ -339,7 +310,7 @@ The content of file B`);
expect(result.contents).toHaveLength(2);
expect(result.contents[0]).toEqual(null);
expect((result.contents[1] as any).value).toMatch(
expect(getValue(result.contents[1])).toMatch(
/^Also referenced in 2 notes:/
);

View File

@@ -2,7 +2,7 @@ import * as vscode from 'vscode';
import { URI } from '../core/model/uri';
import { FoamFeature } from '../types';
import { getNoteTooltip, mdDocSelector, isSome } from '../utils';
import { toVsCodeRange } from '../utils/vsc-utils';
import { fromVsCodeUri, toVsCodeRange } from '../utils/vsc-utils';
import {
ConfigurationMonitor,
monitorFoamVsCodeConfig,
@@ -59,7 +59,10 @@ export class HoverProvider implements vscode.HoverProvider {
return;
}
const startResource = this.parser.parse(document.uri, document.getText());
const startResource = this.parser.parse(
fromVsCodeUri(document.uri),
document.getText()
);
const targetLink: ResourceLink | undefined = startResource.links.find(
link =>
@@ -75,7 +78,7 @@ export class HoverProvider implements vscode.HoverProvider {
const targetUri = this.workspace.resolveLink(startResource, targetLink);
const refs = this.graph
.getBacklinks(targetUri)
.filter(link => !URI.isEqual(link.source, document.uri));
.filter(link => !URI.isEqual(link.source, fromVsCodeUri(document.uri)));
const links = refs.slice(0, 10).map(link => {
const command = OPEN_COMMAND.asURI(link.source);

View File

@@ -8,10 +8,11 @@ import {
createFile,
showInEditor,
} from '../test/test-utils-vscode';
import { fromVsCodeUri } from '../utils/vsc-utils';
import { CompletionProvider } from './link-completion';
describe('Link Completion', () => {
const root = vscode.workspace.workspaceFolders[0].uri;
const root = fromVsCodeUri(vscode.workspace.workspaceFolders[0].uri);
const ws = new FoamWorkspace();
ws.set(
createTestNote({

View File

@@ -9,6 +9,7 @@ import {
ResourceTreeItem,
UriTreeItem,
} from '../utils/grouped-resources-tree-data-provider';
import { fromVsCodeUri } from '../utils/vsc-utils';
const feature: FoamFeature = {
activate: async (
@@ -17,8 +18,8 @@ const feature: FoamFeature = {
) => {
const foam = await foamPromise;
const workspacesURIs = vscode.workspace.workspaceFolders.map(
dir => dir.uri
const workspacesURIs = vscode.workspace.workspaceFolders.map(dir =>
fromVsCodeUri(dir.uri)
);
const provider = new GroupedResourcesTreeDataProvider(

View File

@@ -9,6 +9,7 @@ import {
ResourceTreeItem,
UriTreeItem,
} from '../utils/grouped-resources-tree-data-provider';
import { fromVsCodeUri } from '../utils/vsc-utils';
const feature: FoamFeature = {
activate: async (
@@ -16,8 +17,8 @@ const feature: FoamFeature = {
foamPromise: Promise<Foam>
) => {
const foam = await foamPromise;
const workspacesURIs = vscode.workspace.workspaceFolders.map(
dir => dir.uri
const workspacesURIs = vscode.workspace.workspaceFolders.map(dir =>
fromVsCodeUri(dir.uri)
);
const provider = new GroupedResourcesTreeDataProvider(
'placeholders',

View File

@@ -144,13 +144,22 @@ export const markdownItWithRemoveLinkReferences = (
md: markdownit,
workspace: FoamWorkspace
) => {
// Forget about reference links that contain an alias divider
md.inline.ruler.before('link', 'clear-references', state => {
if (state.env.references) {
Object.keys(state.env.references).forEach(refKey => {
// Forget about reference links that contain an alias divider
// Aliased reference links will lead the MarkdownParser to include wrong link references
if (refKey.includes(ALIAS_DIVIDER_CHAR)) {
delete state.env.references[refKey];
}
// When the reference is present due to an inclusion of that note, we
// need to remove that reference. This ensures the MarkdownIt parser
// will not replace the wikilink syntax with an <a href> link and as a result
// break our inclusion logic.
if (state.src.toLowerCase().includes(`![[${refKey.toLowerCase()}]]`)) {
delete state.env.references[refKey];
}
});
}
return false;

View File

@@ -8,10 +8,11 @@ import {
createFile,
showInEditor,
} from '../test/test-utils-vscode';
import { fromVsCodeUri } from '../utils/vsc-utils';
import { TagCompletionProvider } from './tag-completion';
describe('Tag Completion', () => {
const root = vscode.workspace.workspaceFolders[0].uri;
const root = fromVsCodeUri(vscode.workspace.workspaceFolders[0].uri);
const ws = new FoamWorkspace();
ws.set(
createTestNote({

View File

@@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import { FoamFeature } from '../types';
import { URI } from '../core/model/uri';
import { toVsCodeUri } from '../utils/vsc-utils';
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
import { createNoteForPlaceholderWikilink } from './create-from-template';
export const OPEN_COMMAND = {
@@ -19,18 +19,18 @@ export const OPEN_COMMAND = {
const basedir =
vscode.workspace.workspaceFolders.length > 0
? vscode.workspace.workspaceFolders[0].uri
: vscode.window.activeTextEditor?.document.uri
? URI.getDir(vscode.window.activeTextEditor!.document.uri)
? fromVsCodeUri(vscode.workspace.workspaceFolders[0].uri)
: fromVsCodeUri(vscode.window.activeTextEditor?.document.uri)
? URI.getDir(
fromVsCodeUri(vscode.window.activeTextEditor!.document.uri)
)
: undefined;
if (basedir === undefined) {
return;
}
const target = toVsCodeUri(
URI.createResourceUriFromPlaceholder(basedir, uri)
);
const target = URI.createResourceUriFromPlaceholder(basedir, uri);
await createNoteForPlaceholderWikilink(title, target);
return;

View File

@@ -35,6 +35,7 @@ import {
LINK_REFERENCE_DEFINITION_FOOTER,
LINK_REFERENCE_DEFINITION_HEADER,
} from '../core/janitor';
import { fromVsCodeUri } from '../utils/vsc-utils';
const feature: FoamFeature = {
activate: async (context: ExtensionContext, foamPromise: Promise<Foam>) => {
@@ -73,7 +74,7 @@ const feature: FoamFeature = {
function updateDocumentInNoteGraph(foam: Foam, document: TextDocument) {
foam.workspace.set(
foam.services.parser.parse(document.uri, document.getText())
foam.services.parser.parse(fromVsCodeUri(document.uri), document.getText())
);
}
@@ -139,7 +140,7 @@ function generateReferenceList(
return [];
}
const note = foam.get(doc.uri);
const note = foam.get(fromVsCodeUri(doc.uri));
// Should never happen as `doc` is usually given by `editor.document`, which
// binds to an opened note.

View File

@@ -1,12 +1,15 @@
import { Disposable, workspace } from 'vscode';
import { createConfigFromFolders, FoamConfig } from '../core/config';
import { getIgnoredFilesSetting } from '../settings';
import { fromVsCodeUri } from '../utils/vsc-utils';
// TODO this is still to be improved - foam config should
// not be dependent on vscode but at the moment it's convenient
// to leverage it
export const getConfigFromVscode = (): FoamConfig => {
const workspaceFolders = workspace.workspaceFolders.map(dir => dir.uri);
const workspaceFolders = workspace.workspaceFolders.map(dir =>
fromVsCodeUri(dir.uri)
);
const excludeGlobs = getIgnoredFilesSetting();
return createConfigFromFolders(workspaceFolders, {

View File

@@ -46,6 +46,12 @@ async function main() {
'--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',
});
} catch (err) {
console.log('Error occurred while running Foam e2e tests:', err);
@@ -54,7 +60,7 @@ async function main() {
}
if (!isSuccess) {
process.exit(1);
throw new Error('Some Foam tests failed');
}
}

View File

@@ -15,26 +15,15 @@ import { runCLI } from '@jest/core';
const rootDir = path.resolve(__dirname, '../..');
const bufferLinesAndLog = (out: (value: string) => void) => {
let currentLine = '';
return (buffer: string) => {
const lines = buffer.split(EOL);
const partialLine = lines.pop() ?? '';
if (lines.length > 0) {
const [endOfCurrentLine, ...otherFullLines] = lines;
currentLine += endOfCurrentLine;
[currentLine, ...otherFullLines].forEach(l => out(l));
currentLine = '';
}
currentLine += partialLine;
return true;
};
};
export function run(): Promise<void> {
const errWrite = process.stderr.write;
process.stderr.write = bufferLinesAndLog(console.log.bind(console));
process.stderr.write = (buffer: string) => {
console.log(buffer);
return true;
};
// process.on('unhandledRejection', err => {
// throw err;
// });
process.env.FORCE_COLOR = '1';
process.env.NODE_ENV = 'test';
process.env.BABEL_ENV = 'test';
@@ -74,7 +63,7 @@ export function run(): Promise<void> {
if (failures.length > 0) {
console.log('Some Foam tests failed: ', failures.length);
reject(`Foam e2e tests failed: ${JSON.stringify(failures)}`);
reject(`Some Foam tests failed: ${failures.length}`);
} else {
resolve();
}

View File

@@ -4,7 +4,7 @@
import * as vscode from 'vscode';
import path from 'path';
import { TextEncoder } from 'util';
import { toVsCodeUri } from '../utils/vsc-utils';
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
import { Logger } from '../core/utils/log';
import { URI } from '../core/model/uri';
import { Resource } from '../core/model/note';
@@ -35,12 +35,15 @@ export const closeEditors = async () => {
* @param path relative file path
* @returns an object containing various information about the file created
*/
export const createFile = async (content: string, filepath?: string) => {
const rootUri = vscode.workspace.workspaceFolders[0].uri;
filepath = filepath ?? randomString() + '.md';
const uri = vscode.Uri.joinPath(rootUri, filepath);
const filenameComponents = path.parse(uri.fsPath);
await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(content));
export const createFile = async (content: string, filepath?: string[]) => {
const rootUri = fromVsCodeUri(vscode.workspace.workspaceFolders[0].uri);
filepath = filepath ?? [randomString() + '.md'];
const uri = URI.joinPath(rootUri, ...filepath);
const filenameComponents = path.parse(URI.toFsPath(uri));
await vscode.workspace.fs.writeFile(
toVsCodeUri(uri),
new TextEncoder().encode(content)
);
return { uri, content, ...filenameComponents };
};

View File

@@ -198,12 +198,18 @@ export function getContainsTooltip(titles: string[]): string {
* https://code.visualstudio.com/updates/v1_52#_markdown-tree-tooltip-api
* @param note A Foam Note
*/
export function getNoteTooltip(content: string): MarkdownString {
export function getNoteTooltip(content: string): string {
const STABLE_MARKDOWN_STRING_API_VERSION = '1.52.1';
const strippedContent = stripFrontMatter(stripImages(content));
return formatMarkdownTooltip(strippedContent);
if (version >= STABLE_MARKDOWN_STRING_API_VERSION) {
return formatMarkdownTooltip(strippedContent) as any;
}
return formatSimpleTooltip(strippedContent);
}
function formatMarkdownTooltip(content: string): MarkdownString {
export function formatMarkdownTooltip(content: string): MarkdownString {
const LINES_LIMIT = 16;
const { excerpt, lines } = getExcerpt(content, LINES_LIMIT);
const totalLines = content.split('\n').length;

View File

@@ -1,60 +1,25 @@
import os from 'os';
import { workspace, Uri } from 'vscode';
import { URI } from '../core/model/uri';
import { Uri } from 'vscode';
import { fromVsCodeUri, toVsCodeUri } from './vsc-utils';
describe('uri conversion', () => {
it('uses drive letter casing in windows #488 #507', () => {
if (os.platform() === 'win32') {
const uri = workspace.workspaceFolders[0].uri;
const isDriveUppercase =
uri.fsPath.charCodeAt(0) >= 'A'.charCodeAt(0) &&
uri.fsPath.charCodeAt(0) <= 'Z'.charCodeAt(0);
const [drive, path] = uri.fsPath.split(':');
const posixPath = path.replace(/\\/g, '/');
describe('URI conversion', () => {
it('converts between Foam and VS Code URI', () => {
const vsUnixUri = Uri.file('/this/is/a/path');
const fUnixUri = fromVsCodeUri(vsUnixUri);
expect(toVsCodeUri(fUnixUri)).toEqual(expect.objectContaining(fUnixUri));
const withUppercase = `/${drive.toUpperCase()}:${posixPath}`;
const withLowercase = `/${drive.toLowerCase()}:${posixPath}`;
const expected = isDriveUppercase ? withUppercase : withLowercase;
expect(fromVsCodeUri(Uri.file(withUppercase)).path).toEqual(expected);
expect(fromVsCodeUri(Uri.file(withLowercase)).path).toEqual(expected);
}
});
it('correctly parses file paths', () => {
const test = workspace.workspaceFolders[0].uri;
const uri = URI.file(test.fsPath);
expect(uri).toEqual(
URI.create({
scheme: 'file',
path: test.path,
})
const vsWinUpperDriveUri = Uri.file('C:\\this\\is\\a\\path');
const fWinUpperUri = fromVsCodeUri(vsWinUpperDriveUri);
expect(toVsCodeUri(fWinUpperUri)).toEqual(
expect.objectContaining(fWinUpperUri)
);
});
it('creates a proper string representation for file uris', () => {
const test = workspace.workspaceFolders[0].uri;
const uri = URI.file(test.fsPath);
expect(URI.toString(uri)).toEqual(test.toString());
});
it('is consistent when converting from VS Code to Foam URI', () => {
const vsUri = workspace.workspaceFolders[0].uri;
const fUri = fromVsCodeUri(vsUri);
expect(toVsCodeUri(fUri)).toEqual(expect.objectContaining(fUri));
});
it('is consistent when converting from Foam to VS Code URI', () => {
const test = workspace.workspaceFolders[0].uri;
const uri = URI.file(test.fsPath);
const fUri = toVsCodeUri(uri);
expect(fUri).toEqual(
const vsWinLowerUri = Uri.file('c:\\this\\is\\a\\path');
const fWinLowerUri = fromVsCodeUri(vsWinLowerUri);
expect(toVsCodeUri(fWinLowerUri)).toEqual(
expect.objectContaining({
scheme: 'file',
path: test.path,
...fWinLowerUri,
path: fWinUpperUri.path, // path is normalized to upper case
})
);
expect(fromVsCodeUri(fUri)).toEqual(uri);
});
});

View File

@@ -188,6 +188,7 @@ const Actions = {
function initDataviz(channel) {
const elem = document.getElementById(CONTAINER_ID);
const painter = new Painter();
graph(elem)
.graphData(model.data)
.backgroundColor(model.style.background)
@@ -222,10 +223,12 @@ function initDataviz(channel) {
});
const label = info.title;
Draw(ctx)
.circle(node.x, node.y, size + 0.2, border)
.circle(node.x, node.y, size, fill)
.text(label, node.x, node.y + size + 1, fontSize, textColor.toString());
painter
.circle(node.x, node.y, size, fill, border)
.text(label, node.x, node.y + size + 1, fontSize, textColor);
})
.onRenderFramePost(ctx => {
painter.paint(ctx);
})
.linkColor(link => getLinkColor(link, model))
.onNodeHover(node => {
@@ -402,24 +405,69 @@ function getLinkState(link, model) {
: 'lessened';
}
const Draw = ctx => ({
circle: function(x, y, radius, color) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
class Painter {
circlesByColor = new Map();
bordersByColor = new Map();
texts = [];
_addCircle(x, y, radius, color, isBorder = false) {
if (color.opacity > 0) {
const target = isBorder ? this.bordersByColor : this.circlesByColor;
if (!target.has(color)) {
target.set(color, []);
}
target.get(color).push({ x, y, radius });
}
}
_areSameColor(a, b) {
return a.r === b.r && a.g === b.g && a.b === b.b && a.opacity === b.opacity;
}
circle(x, y, radius, fill, border) {
this._addCircle(x, y, radius + 0.2, border, true);
if (!this._areSameColor(border, fill)) {
this._addCircle(x, y, radius, fill);
}
return this;
},
text: function(text, x, y, size, color) {
ctx.font = `${size}px Sans-Serif`;
}
text(text, x, y, size, color) {
if (color.opacity > 0) {
this.texts.push({ x, y, text, size, color });
}
return this;
}
paint(ctx) {
// Draw nodes
// first draw borders, then draw contents over them
for (const target of [this.bordersByColor, this.circlesByColor]) {
for (const [color, circles] of target.entries()) {
ctx.beginPath();
ctx.fillStyle = color;
for (const circle of circles) {
ctx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI, false);
}
ctx.closePath();
ctx.fill();
}
target.clear();
}
// Draw labels
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillStyle = color;
ctx.fillText(text, x, y);
for (const text of this.texts) {
ctx.font = `${text.size}px Sans-Serif`;
ctx.fillStyle = text.color;
ctx.fillText(text.text, text.x, text.y);
}
this.texts = [];
return this;
},
});
}
}
// init the app
try {

View File

@@ -0,0 +1,5 @@
# Note being refered as angel
This is just a link target for now.
We can use it for other things later if needed.

View File

@@ -0,0 +1,3 @@
# Angel reference
[[Note being refered as angel]]

View File

@@ -0,0 +1,11 @@
# File with explicit link references
A Bug [^footerlink]. Here is [Another link][linkreference].
I also want a [[first-document]].
[^footerlink]: https://foambubble.github.io/
[linkrefenrece]: https://foambubble.github.io/
[//begin]: # 'Autogenerated link references for markdown compatibility'
[first-document]: first-document 'First Document'
[//end]: # 'Autogenerated link references'

View File

@@ -0,0 +1,7 @@
# File with explicit link references
A Bug [^footerlink]. Here is [Another link][linkreference]
[^footerlink]: https://foambubble.github.io/
[linkrefenrece]: https://foambubble.github.io/

View File

@@ -5,7 +5,7 @@
👀*This is an early stage project under rapid development. For updates join the [Foam community Discord](https://foambubble.github.io/join-discord/g)! 💬*
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-82-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-85-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
[![Discord Chat](https://img.shields.io/discord/729975036148056075?color=748AD9&label=discord%20chat&style=flat-square)](https://foambubble.github.io/join-discord/g)
@@ -167,6 +167,11 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="http://www.prashu.com"><img src="https://avatars.githubusercontent.com/u/476729?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Prashanth Subrahmanyam</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=ksprashu" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/JonasSprenger"><img src="https://avatars.githubusercontent.com/u/25108895?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Jonas SPRENGER</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=JonasSprenger" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Laptop765"><img src="https://avatars.githubusercontent.com/u/1468359?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Paul</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=Laptop765" title="Documentation">📖</a></td>
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=eltociear" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/memeplex"><img src="https://avatars.githubusercontent.com/u/2845433?v=4?s=60" width="60px;" alt=""/><br /><sub><b>memeplex</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=memeplex" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/AndreiD049"><img src="https://avatars.githubusercontent.com/u/52671223?v=4?s=60" width="60px;" alt=""/><br /><sub><b>AndreiD049</b></sub></a><br /><a href="https://github.com/foambubble/foam/commits?author=AndreiD049" title="Code">💻</a></td>
</tr>
</table>

View File

@@ -2417,10 +2417,10 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
"@types/vscode@^1.61.0":
version "1.61.0"
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.61.0.tgz#c54335b6f84c19c69b1435b17cc0ce3b2cecfeec"
integrity sha512-9k5Nwq45hkRwdfCFY+eKXeQQSbPoA114mF7U/4uJXRBJeGIO7MuJdhF1PnaDN+lllL9iKGQtd6FFXShBXMNaFg==
"@types/vscode@^1.47.1":
version "1.54.0"
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.54.0.tgz#d28e3b3614054b2d6543c29412f60a986cabd9bb"
integrity sha512-sHHw9HG4bTrnKhLGgmEiOS88OLO/2RQytUN4COX9Djv81zc0FSZsSiYaVyjNidDzUSpXsySKBkZ31lk2/FbdCg==
"@types/yargs-parser@*":
version "20.2.0"