mirror of
https://github.com/foambubble/foam.git
synced 2026-01-09 14:08:13 -05:00
Using URI across the board for better Windows support (#391)
* add windows-2019 to CI os matrix * use actual URI object instead of strings to represent paths/uris * added datastore tests * chore: make Foam IDisposable
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
name: Build and Test
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-10.15, ubuntu-18.04] # add windows-2019 after fixing tests for it
|
||||
os: [macos-10.15, ubuntu-18.04, windows-2019]
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'foambubble/foam'
|
||||
env:
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
applyTextEdit,
|
||||
Services,
|
||||
FileDataStore,
|
||||
URI,
|
||||
} from 'foam-core';
|
||||
import { writeFileToDisk } from '../utils/write-file-to-disk';
|
||||
import { isValidDirectory } from '../utils';
|
||||
@@ -40,7 +41,7 @@ export default class Janitor extends Command {
|
||||
const { workspacePath = './' } = args;
|
||||
|
||||
if (isValidDirectory(workspacePath)) {
|
||||
const config = createConfigFromFolders([workspacePath]);
|
||||
const config = createConfigFromFolders([URI.file(workspacePath)]);
|
||||
const services: Services = {
|
||||
dataStore: new FileDataStore(config),
|
||||
};
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { URI } from 'foam-core';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fileUri absolute path for the file that needs to renamed
|
||||
* @param newFileName "new file name" without the extension
|
||||
*/
|
||||
export const renameFile = async (fileUri: string, newFileName: string) => {
|
||||
const dirName = path.dirname(fileUri);
|
||||
const extension = path.extname(fileUri);
|
||||
export const renameFile = async (fileUri: URI, newFileName: string) => {
|
||||
const filePath = fileUri.fsPath;
|
||||
const dirName = path.dirname(filePath);
|
||||
const extension = path.extname(filePath);
|
||||
const newFileUri = path.join(dirName, `${newFileName}${extension}`);
|
||||
|
||||
return fs.promises.rename(fileUri, newFileUri);
|
||||
return fs.promises.rename(filePath, newFileUri);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as fs from 'fs';
|
||||
import { URI } from 'foam-core';
|
||||
|
||||
export const writeFileToDisk = async (fileUri: string, data: string) => {
|
||||
return fs.promises.writeFile(fileUri, data);
|
||||
export const writeFileToDisk = async (fileUri: URI, data: string) => {
|
||||
return fs.promises.writeFile(fileUri.fsPath, data);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { renameFile } from '../src/utils/rename-file';
|
||||
import * as fs from 'fs';
|
||||
import mockFS from 'mock-fs';
|
||||
import { URI } from 'foam-core';
|
||||
|
||||
const doesFileExist = path =>
|
||||
fs.promises
|
||||
@@ -9,10 +10,10 @@ const doesFileExist = path =>
|
||||
.catch(() => false);
|
||||
|
||||
describe('renameFile', () => {
|
||||
const fileUri = './test/oldFileName.md';
|
||||
const fileUri = URI.file('/test/oldFileName.md');
|
||||
|
||||
beforeAll(() => {
|
||||
mockFS({ [fileUri]: '' });
|
||||
mockFS({ [fileUri.fsPath]: '' });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@@ -20,11 +21,11 @@ describe('renameFile', () => {
|
||||
});
|
||||
|
||||
it('should rename existing file', async () => {
|
||||
expect(await doesFileExist(fileUri)).toBe(true);
|
||||
expect(await doesFileExist(fileUri.fsPath)).toBe(true);
|
||||
|
||||
renameFile(fileUri, 'new-file-name');
|
||||
|
||||
expect(await doesFileExist(fileUri)).toBe(false);
|
||||
expect(await doesFileExist('./test/new-file-name.md')).toBe(true);
|
||||
expect(await doesFileExist(fileUri.fsPath)).toBe(false);
|
||||
expect(await doesFileExist('/test/new-file-name.md')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import { writeFileToDisk } from '../src/utils/write-file-to-disk';
|
||||
import * as fs from 'fs';
|
||||
import mockFS from 'mock-fs';
|
||||
import { URI } from 'foam-core';
|
||||
|
||||
describe('writeFileToDisk', () => {
|
||||
const fileUri = './test-file.md';
|
||||
const fileUri = URI.file('./test-file.md');
|
||||
|
||||
beforeAll(() => {
|
||||
mockFS({ [fileUri]: 'content in the existing file' });
|
||||
mockFS({ [fileUri.fsPath]: 'content in the existing file' });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
fs.unlinkSync(fileUri);
|
||||
fs.unlinkSync(fileUri.fsPath);
|
||||
mockFS.restore();
|
||||
});
|
||||
|
||||
it('should overrwrite existing file in the disk with the new data', async () => {
|
||||
const expected = `content in the new file`;
|
||||
await writeFileToDisk(fileUri, expected);
|
||||
const actual = await fs.promises.readFile(fileUri, { encoding: 'utf8' });
|
||||
const actual = await fs.promises.readFile(fileUri.fsPath, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { FoamConfig, Foam, Services } from './index';
|
||||
import { loadPlugins } from './plugins';
|
||||
import { isSome } from './utils';
|
||||
import { isDisposable } from './common/lifecycle';
|
||||
import { Logger } from './utils/log';
|
||||
|
||||
export const bootstrap = async (config: FoamConfig, services: Services) => {
|
||||
const plugins = await loadPlugins(config);
|
||||
@@ -17,9 +18,12 @@ export const bootstrap = async (config: FoamConfig, services: Services) => {
|
||||
const files = await services.dataStore.listFiles();
|
||||
await Promise.all(
|
||||
files.map(async uri => {
|
||||
const content = await services.dataStore.read(uri);
|
||||
if (isSome(content)) {
|
||||
graph.setNote(parser.parse(uri, content));
|
||||
Logger.info('Found: ' + uri);
|
||||
if (uri.path.endsWith('md')) {
|
||||
const content = await services.dataStore.read(uri);
|
||||
if (isSome(content)) {
|
||||
graph.setNote(parser.parse(uri, content));
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
436
packages/foam-core/src/common/charCode.ts
Normal file
436
packages/foam-core/src/common/charCode.ts
Normal file
@@ -0,0 +1,436 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
|
||||
// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/
|
||||
|
||||
/**
|
||||
* An inlined enum containing useful character codes (to be used with String.charCodeAt).
|
||||
* Please leave the const keyword such that it gets inlined when compiled to JavaScript!
|
||||
*/
|
||||
export const enum CharCode {
|
||||
Null = 0,
|
||||
/**
|
||||
* The `\b` character.
|
||||
*/
|
||||
Backspace = 8,
|
||||
/**
|
||||
* The `\t` character.
|
||||
*/
|
||||
Tab = 9,
|
||||
/**
|
||||
* The `\n` character.
|
||||
*/
|
||||
LineFeed = 10,
|
||||
/**
|
||||
* The `\r` character.
|
||||
*/
|
||||
CarriageReturn = 13,
|
||||
Space = 32,
|
||||
/**
|
||||
* The `!` character.
|
||||
*/
|
||||
ExclamationMark = 33,
|
||||
/**
|
||||
* The `"` character.
|
||||
*/
|
||||
DoubleQuote = 34,
|
||||
/**
|
||||
* The `#` character.
|
||||
*/
|
||||
Hash = 35,
|
||||
/**
|
||||
* The `$` character.
|
||||
*/
|
||||
DollarSign = 36,
|
||||
/**
|
||||
* The `%` character.
|
||||
*/
|
||||
PercentSign = 37,
|
||||
/**
|
||||
* The `&` character.
|
||||
*/
|
||||
Ampersand = 38,
|
||||
/**
|
||||
* The `'` character.
|
||||
*/
|
||||
SingleQuote = 39,
|
||||
/**
|
||||
* The `(` character.
|
||||
*/
|
||||
OpenParen = 40,
|
||||
/**
|
||||
* The `)` character.
|
||||
*/
|
||||
CloseParen = 41,
|
||||
/**
|
||||
* The `*` character.
|
||||
*/
|
||||
Asterisk = 42,
|
||||
/**
|
||||
* The `+` character.
|
||||
*/
|
||||
Plus = 43,
|
||||
/**
|
||||
* The `,` character.
|
||||
*/
|
||||
Comma = 44,
|
||||
/**
|
||||
* The `-` character.
|
||||
*/
|
||||
Dash = 45,
|
||||
/**
|
||||
* The `.` character.
|
||||
*/
|
||||
Period = 46,
|
||||
/**
|
||||
* The `/` character.
|
||||
*/
|
||||
Slash = 47,
|
||||
|
||||
Digit0 = 48,
|
||||
Digit1 = 49,
|
||||
Digit2 = 50,
|
||||
Digit3 = 51,
|
||||
Digit4 = 52,
|
||||
Digit5 = 53,
|
||||
Digit6 = 54,
|
||||
Digit7 = 55,
|
||||
Digit8 = 56,
|
||||
Digit9 = 57,
|
||||
|
||||
/**
|
||||
* The `:` character.
|
||||
*/
|
||||
Colon = 58,
|
||||
/**
|
||||
* The `;` character.
|
||||
*/
|
||||
Semicolon = 59,
|
||||
/**
|
||||
* The `<` character.
|
||||
*/
|
||||
LessThan = 60,
|
||||
/**
|
||||
* The `=` character.
|
||||
*/
|
||||
Equals = 61,
|
||||
/**
|
||||
* The `>` character.
|
||||
*/
|
||||
GreaterThan = 62,
|
||||
/**
|
||||
* The `?` character.
|
||||
*/
|
||||
QuestionMark = 63,
|
||||
/**
|
||||
* The `@` character.
|
||||
*/
|
||||
AtSign = 64,
|
||||
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
S = 83,
|
||||
T = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
|
||||
/**
|
||||
* The `[` character.
|
||||
*/
|
||||
OpenSquareBracket = 91,
|
||||
/**
|
||||
* The `\` character.
|
||||
*/
|
||||
Backslash = 92,
|
||||
/**
|
||||
* The `]` character.
|
||||
*/
|
||||
CloseSquareBracket = 93,
|
||||
/**
|
||||
* The `^` character.
|
||||
*/
|
||||
Caret = 94,
|
||||
/**
|
||||
* The `_` character.
|
||||
*/
|
||||
Underline = 95,
|
||||
/**
|
||||
* The ``(`)`` character.
|
||||
*/
|
||||
BackTick = 96,
|
||||
|
||||
a = 97,
|
||||
b = 98,
|
||||
c = 99,
|
||||
d = 100,
|
||||
e = 101,
|
||||
f = 102,
|
||||
g = 103,
|
||||
h = 104,
|
||||
i = 105,
|
||||
j = 106,
|
||||
k = 107,
|
||||
l = 108,
|
||||
m = 109,
|
||||
n = 110,
|
||||
o = 111,
|
||||
p = 112,
|
||||
q = 113,
|
||||
r = 114,
|
||||
s = 115,
|
||||
t = 116,
|
||||
u = 117,
|
||||
v = 118,
|
||||
w = 119,
|
||||
x = 120,
|
||||
y = 121,
|
||||
z = 122,
|
||||
|
||||
/**
|
||||
* The `{` character.
|
||||
*/
|
||||
OpenCurlyBrace = 123,
|
||||
/**
|
||||
* The `|` character.
|
||||
*/
|
||||
Pipe = 124,
|
||||
/**
|
||||
* The `}` character.
|
||||
*/
|
||||
CloseCurlyBrace = 125,
|
||||
/**
|
||||
* The `~` character.
|
||||
*/
|
||||
Tilde = 126,
|
||||
|
||||
U_Combining_Grave_Accent = 0x0300, // U+0300 Combining Grave Accent
|
||||
U_Combining_Acute_Accent = 0x0301, // U+0301 Combining Acute Accent
|
||||
U_Combining_Circumflex_Accent = 0x0302, // U+0302 Combining Circumflex Accent
|
||||
U_Combining_Tilde = 0x0303, // U+0303 Combining Tilde
|
||||
U_Combining_Macron = 0x0304, // U+0304 Combining Macron
|
||||
U_Combining_Overline = 0x0305, // U+0305 Combining Overline
|
||||
U_Combining_Breve = 0x0306, // U+0306 Combining Breve
|
||||
U_Combining_Dot_Above = 0x0307, // U+0307 Combining Dot Above
|
||||
U_Combining_Diaeresis = 0x0308, // U+0308 Combining Diaeresis
|
||||
U_Combining_Hook_Above = 0x0309, // U+0309 Combining Hook Above
|
||||
U_Combining_Ring_Above = 0x030a, // U+030A Combining Ring Above
|
||||
U_Combining_Double_Acute_Accent = 0x030b, // U+030B Combining Double Acute Accent
|
||||
U_Combining_Caron = 0x030c, // U+030C Combining Caron
|
||||
U_Combining_Vertical_Line_Above = 0x030d, // U+030D Combining Vertical Line Above
|
||||
U_Combining_Double_Vertical_Line_Above = 0x030e, // U+030E Combining Double Vertical Line Above
|
||||
U_Combining_Double_Grave_Accent = 0x030f, // U+030F Combining Double Grave Accent
|
||||
U_Combining_Candrabindu = 0x0310, // U+0310 Combining Candrabindu
|
||||
U_Combining_Inverted_Breve = 0x0311, // U+0311 Combining Inverted Breve
|
||||
U_Combining_Turned_Comma_Above = 0x0312, // U+0312 Combining Turned Comma Above
|
||||
U_Combining_Comma_Above = 0x0313, // U+0313 Combining Comma Above
|
||||
U_Combining_Reversed_Comma_Above = 0x0314, // U+0314 Combining Reversed Comma Above
|
||||
U_Combining_Comma_Above_Right = 0x0315, // U+0315 Combining Comma Above Right
|
||||
U_Combining_Grave_Accent_Below = 0x0316, // U+0316 Combining Grave Accent Below
|
||||
U_Combining_Acute_Accent_Below = 0x0317, // U+0317 Combining Acute Accent Below
|
||||
U_Combining_Left_Tack_Below = 0x0318, // U+0318 Combining Left Tack Below
|
||||
U_Combining_Right_Tack_Below = 0x0319, // U+0319 Combining Right Tack Below
|
||||
U_Combining_Left_Angle_Above = 0x031a, // U+031A Combining Left Angle Above
|
||||
U_Combining_Horn = 0x031b, // U+031B Combining Horn
|
||||
U_Combining_Left_Half_Ring_Below = 0x031c, // U+031C Combining Left Half Ring Below
|
||||
U_Combining_Up_Tack_Below = 0x031d, // U+031D Combining Up Tack Below
|
||||
U_Combining_Down_Tack_Below = 0x031e, // U+031E Combining Down Tack Below
|
||||
U_Combining_Plus_Sign_Below = 0x031f, // U+031F Combining Plus Sign Below
|
||||
U_Combining_Minus_Sign_Below = 0x0320, // U+0320 Combining Minus Sign Below
|
||||
U_Combining_Palatalized_Hook_Below = 0x0321, // U+0321 Combining Palatalized Hook Below
|
||||
U_Combining_Retroflex_Hook_Below = 0x0322, // U+0322 Combining Retroflex Hook Below
|
||||
U_Combining_Dot_Below = 0x0323, // U+0323 Combining Dot Below
|
||||
U_Combining_Diaeresis_Below = 0x0324, // U+0324 Combining Diaeresis Below
|
||||
U_Combining_Ring_Below = 0x0325, // U+0325 Combining Ring Below
|
||||
U_Combining_Comma_Below = 0x0326, // U+0326 Combining Comma Below
|
||||
U_Combining_Cedilla = 0x0327, // U+0327 Combining Cedilla
|
||||
U_Combining_Ogonek = 0x0328, // U+0328 Combining Ogonek
|
||||
U_Combining_Vertical_Line_Below = 0x0329, // U+0329 Combining Vertical Line Below
|
||||
U_Combining_Bridge_Below = 0x032a, // U+032A Combining Bridge Below
|
||||
U_Combining_Inverted_Double_Arch_Below = 0x032b, // U+032B Combining Inverted Double Arch Below
|
||||
U_Combining_Caron_Below = 0x032c, // U+032C Combining Caron Below
|
||||
U_Combining_Circumflex_Accent_Below = 0x032d, // U+032D Combining Circumflex Accent Below
|
||||
U_Combining_Breve_Below = 0x032e, // U+032E Combining Breve Below
|
||||
U_Combining_Inverted_Breve_Below = 0x032f, // U+032F Combining Inverted Breve Below
|
||||
U_Combining_Tilde_Below = 0x0330, // U+0330 Combining Tilde Below
|
||||
U_Combining_Macron_Below = 0x0331, // U+0331 Combining Macron Below
|
||||
U_Combining_Low_Line = 0x0332, // U+0332 Combining Low Line
|
||||
U_Combining_Double_Low_Line = 0x0333, // U+0333 Combining Double Low Line
|
||||
U_Combining_Tilde_Overlay = 0x0334, // U+0334 Combining Tilde Overlay
|
||||
U_Combining_Short_Stroke_Overlay = 0x0335, // U+0335 Combining Short Stroke Overlay
|
||||
U_Combining_Long_Stroke_Overlay = 0x0336, // U+0336 Combining Long Stroke Overlay
|
||||
U_Combining_Short_Solidus_Overlay = 0x0337, // U+0337 Combining Short Solidus Overlay
|
||||
U_Combining_Long_Solidus_Overlay = 0x0338, // U+0338 Combining Long Solidus Overlay
|
||||
U_Combining_Right_Half_Ring_Below = 0x0339, // U+0339 Combining Right Half Ring Below
|
||||
U_Combining_Inverted_Bridge_Below = 0x033a, // U+033A Combining Inverted Bridge Below
|
||||
U_Combining_Square_Below = 0x033b, // U+033B Combining Square Below
|
||||
U_Combining_Seagull_Below = 0x033c, // U+033C Combining Seagull Below
|
||||
U_Combining_X_Above = 0x033d, // U+033D Combining X Above
|
||||
U_Combining_Vertical_Tilde = 0x033e, // U+033E Combining Vertical Tilde
|
||||
U_Combining_Double_Overline = 0x033f, // U+033F Combining Double Overline
|
||||
U_Combining_Grave_Tone_Mark = 0x0340, // U+0340 Combining Grave Tone Mark
|
||||
U_Combining_Acute_Tone_Mark = 0x0341, // U+0341 Combining Acute Tone Mark
|
||||
U_Combining_Greek_Perispomeni = 0x0342, // U+0342 Combining Greek Perispomeni
|
||||
U_Combining_Greek_Koronis = 0x0343, // U+0343 Combining Greek Koronis
|
||||
U_Combining_Greek_Dialytika_Tonos = 0x0344, // U+0344 Combining Greek Dialytika Tonos
|
||||
U_Combining_Greek_Ypogegrammeni = 0x0345, // U+0345 Combining Greek Ypogegrammeni
|
||||
U_Combining_Bridge_Above = 0x0346, // U+0346 Combining Bridge Above
|
||||
U_Combining_Equals_Sign_Below = 0x0347, // U+0347 Combining Equals Sign Below
|
||||
U_Combining_Double_Vertical_Line_Below = 0x0348, // U+0348 Combining Double Vertical Line Below
|
||||
U_Combining_Left_Angle_Below = 0x0349, // U+0349 Combining Left Angle Below
|
||||
U_Combining_Not_Tilde_Above = 0x034a, // U+034A Combining Not Tilde Above
|
||||
U_Combining_Homothetic_Above = 0x034b, // U+034B Combining Homothetic Above
|
||||
U_Combining_Almost_Equal_To_Above = 0x034c, // U+034C Combining Almost Equal To Above
|
||||
U_Combining_Left_Right_Arrow_Below = 0x034d, // U+034D Combining Left Right Arrow Below
|
||||
U_Combining_Upwards_Arrow_Below = 0x034e, // U+034E Combining Upwards Arrow Below
|
||||
U_Combining_Grapheme_Joiner = 0x034f, // U+034F Combining Grapheme Joiner
|
||||
U_Combining_Right_Arrowhead_Above = 0x0350, // U+0350 Combining Right Arrowhead Above
|
||||
U_Combining_Left_Half_Ring_Above = 0x0351, // U+0351 Combining Left Half Ring Above
|
||||
U_Combining_Fermata = 0x0352, // U+0352 Combining Fermata
|
||||
U_Combining_X_Below = 0x0353, // U+0353 Combining X Below
|
||||
U_Combining_Left_Arrowhead_Below = 0x0354, // U+0354 Combining Left Arrowhead Below
|
||||
U_Combining_Right_Arrowhead_Below = 0x0355, // U+0355 Combining Right Arrowhead Below
|
||||
U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356, // U+0356 Combining Right Arrowhead And Up Arrowhead Below
|
||||
U_Combining_Right_Half_Ring_Above = 0x0357, // U+0357 Combining Right Half Ring Above
|
||||
U_Combining_Dot_Above_Right = 0x0358, // U+0358 Combining Dot Above Right
|
||||
U_Combining_Asterisk_Below = 0x0359, // U+0359 Combining Asterisk Below
|
||||
U_Combining_Double_Ring_Below = 0x035a, // U+035A Combining Double Ring Below
|
||||
U_Combining_Zigzag_Above = 0x035b, // U+035B Combining Zigzag Above
|
||||
U_Combining_Double_Breve_Below = 0x035c, // U+035C Combining Double Breve Below
|
||||
U_Combining_Double_Breve = 0x035d, // U+035D Combining Double Breve
|
||||
U_Combining_Double_Macron = 0x035e, // U+035E Combining Double Macron
|
||||
U_Combining_Double_Macron_Below = 0x035f, // U+035F Combining Double Macron Below
|
||||
U_Combining_Double_Tilde = 0x0360, // U+0360 Combining Double Tilde
|
||||
U_Combining_Double_Inverted_Breve = 0x0361, // U+0361 Combining Double Inverted Breve
|
||||
U_Combining_Double_Rightwards_Arrow_Below = 0x0362, // U+0362 Combining Double Rightwards Arrow Below
|
||||
U_Combining_Latin_Small_Letter_A = 0x0363, // U+0363 Combining Latin Small Letter A
|
||||
U_Combining_Latin_Small_Letter_E = 0x0364, // U+0364 Combining Latin Small Letter E
|
||||
U_Combining_Latin_Small_Letter_I = 0x0365, // U+0365 Combining Latin Small Letter I
|
||||
U_Combining_Latin_Small_Letter_O = 0x0366, // U+0366 Combining Latin Small Letter O
|
||||
U_Combining_Latin_Small_Letter_U = 0x0367, // U+0367 Combining Latin Small Letter U
|
||||
U_Combining_Latin_Small_Letter_C = 0x0368, // U+0368 Combining Latin Small Letter C
|
||||
U_Combining_Latin_Small_Letter_D = 0x0369, // U+0369 Combining Latin Small Letter D
|
||||
U_Combining_Latin_Small_Letter_H = 0x036a, // U+036A Combining Latin Small Letter H
|
||||
U_Combining_Latin_Small_Letter_M = 0x036b, // U+036B Combining Latin Small Letter M
|
||||
U_Combining_Latin_Small_Letter_R = 0x036c, // U+036C Combining Latin Small Letter R
|
||||
U_Combining_Latin_Small_Letter_T = 0x036d, // U+036D Combining Latin Small Letter T
|
||||
U_Combining_Latin_Small_Letter_V = 0x036e, // U+036E Combining Latin Small Letter V
|
||||
U_Combining_Latin_Small_Letter_X = 0x036f, // U+036F Combining Latin Small Letter X
|
||||
|
||||
/**
|
||||
* Unicode Character 'LINE SEPARATOR' (U+2028)
|
||||
* http://www.fileformat.info/info/unicode/char/2028/index.htm
|
||||
*/
|
||||
LINE_SEPARATOR = 0x2028,
|
||||
/**
|
||||
* Unicode Character 'PARAGRAPH SEPARATOR' (U+2029)
|
||||
* http://www.fileformat.info/info/unicode/char/2029/index.htm
|
||||
*/
|
||||
PARAGRAPH_SEPARATOR = 0x2029,
|
||||
/**
|
||||
* Unicode Character 'NEXT LINE' (U+0085)
|
||||
* http://www.fileformat.info/info/unicode/char/0085/index.htm
|
||||
*/
|
||||
NEXT_LINE = 0x0085,
|
||||
|
||||
// http://www.fileformat.info/info/unicode/category/Sk/list.htm
|
||||
U_CIRCUMFLEX = 0x005e, // U+005E CIRCUMFLEX
|
||||
U_GRAVE_ACCENT = 0x0060, // U+0060 GRAVE ACCENT
|
||||
U_DIAERESIS = 0x00a8, // U+00A8 DIAERESIS
|
||||
U_MACRON = 0x00af, // U+00AF MACRON
|
||||
U_ACUTE_ACCENT = 0x00b4, // U+00B4 ACUTE ACCENT
|
||||
U_CEDILLA = 0x00b8, // U+00B8 CEDILLA
|
||||
U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02c2, // U+02C2 MODIFIER LETTER LEFT ARROWHEAD
|
||||
U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02c3, // U+02C3 MODIFIER LETTER RIGHT ARROWHEAD
|
||||
U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02c4, // U+02C4 MODIFIER LETTER UP ARROWHEAD
|
||||
U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02c5, // U+02C5 MODIFIER LETTER DOWN ARROWHEAD
|
||||
U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02d2, // U+02D2 MODIFIER LETTER CENTRED RIGHT HALF RING
|
||||
U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02d3, // U+02D3 MODIFIER LETTER CENTRED LEFT HALF RING
|
||||
U_MODIFIER_LETTER_UP_TACK = 0x02d4, // U+02D4 MODIFIER LETTER UP TACK
|
||||
U_MODIFIER_LETTER_DOWN_TACK = 0x02d5, // U+02D5 MODIFIER LETTER DOWN TACK
|
||||
U_MODIFIER_LETTER_PLUS_SIGN = 0x02d6, // U+02D6 MODIFIER LETTER PLUS SIGN
|
||||
U_MODIFIER_LETTER_MINUS_SIGN = 0x02d7, // U+02D7 MODIFIER LETTER MINUS SIGN
|
||||
U_BREVE = 0x02d8, // U+02D8 BREVE
|
||||
U_DOT_ABOVE = 0x02d9, // U+02D9 DOT ABOVE
|
||||
U_RING_ABOVE = 0x02da, // U+02DA RING ABOVE
|
||||
U_OGONEK = 0x02db, // U+02DB OGONEK
|
||||
U_SMALL_TILDE = 0x02dc, // U+02DC SMALL TILDE
|
||||
U_DOUBLE_ACUTE_ACCENT = 0x02dd, // U+02DD DOUBLE ACUTE ACCENT
|
||||
U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02de, // U+02DE MODIFIER LETTER RHOTIC HOOK
|
||||
U_MODIFIER_LETTER_CROSS_ACCENT = 0x02df, // U+02DF MODIFIER LETTER CROSS ACCENT
|
||||
U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02e5, // U+02E5 MODIFIER LETTER EXTRA-HIGH TONE BAR
|
||||
U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02e6, // U+02E6 MODIFIER LETTER HIGH TONE BAR
|
||||
U_MODIFIER_LETTER_MID_TONE_BAR = 0x02e7, // U+02E7 MODIFIER LETTER MID TONE BAR
|
||||
U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02e8, // U+02E8 MODIFIER LETTER LOW TONE BAR
|
||||
U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02e9, // U+02E9 MODIFIER LETTER EXTRA-LOW TONE BAR
|
||||
U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02ea, // U+02EA MODIFIER LETTER YIN DEPARTING TONE MARK
|
||||
U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02eb, // U+02EB MODIFIER LETTER YANG DEPARTING TONE MARK
|
||||
U_MODIFIER_LETTER_UNASPIRATED = 0x02ed, // U+02ED MODIFIER LETTER UNASPIRATED
|
||||
U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02ef, // U+02EF MODIFIER LETTER LOW DOWN ARROWHEAD
|
||||
U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02f0, // U+02F0 MODIFIER LETTER LOW UP ARROWHEAD
|
||||
U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02f1, // U+02F1 MODIFIER LETTER LOW LEFT ARROWHEAD
|
||||
U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02f2, // U+02F2 MODIFIER LETTER LOW RIGHT ARROWHEAD
|
||||
U_MODIFIER_LETTER_LOW_RING = 0x02f3, // U+02F3 MODIFIER LETTER LOW RING
|
||||
U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02f4, // U+02F4 MODIFIER LETTER MIDDLE GRAVE ACCENT
|
||||
U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02f5, // U+02F5 MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT
|
||||
U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02f6, // U+02F6 MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT
|
||||
U_MODIFIER_LETTER_LOW_TILDE = 0x02f7, // U+02F7 MODIFIER LETTER LOW TILDE
|
||||
U_MODIFIER_LETTER_RAISED_COLON = 0x02f8, // U+02F8 MODIFIER LETTER RAISED COLON
|
||||
U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02f9, // U+02F9 MODIFIER LETTER BEGIN HIGH TONE
|
||||
U_MODIFIER_LETTER_END_HIGH_TONE = 0x02fa, // U+02FA MODIFIER LETTER END HIGH TONE
|
||||
U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02fb, // U+02FB MODIFIER LETTER BEGIN LOW TONE
|
||||
U_MODIFIER_LETTER_END_LOW_TONE = 0x02fc, // U+02FC MODIFIER LETTER END LOW TONE
|
||||
U_MODIFIER_LETTER_SHELF = 0x02fd, // U+02FD MODIFIER LETTER SHELF
|
||||
U_MODIFIER_LETTER_OPEN_SHELF = 0x02fe, // U+02FE MODIFIER LETTER OPEN SHELF
|
||||
U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02ff, // U+02FF MODIFIER LETTER LOW LEFT ARROW
|
||||
U_GREEK_LOWER_NUMERAL_SIGN = 0x0375, // U+0375 GREEK LOWER NUMERAL SIGN
|
||||
U_GREEK_TONOS = 0x0384, // U+0384 GREEK TONOS
|
||||
U_GREEK_DIALYTIKA_TONOS = 0x0385, // U+0385 GREEK DIALYTIKA TONOS
|
||||
U_GREEK_KORONIS = 0x1fbd, // U+1FBD GREEK KORONIS
|
||||
U_GREEK_PSILI = 0x1fbf, // U+1FBF GREEK PSILI
|
||||
U_GREEK_PERISPOMENI = 0x1fc0, // U+1FC0 GREEK PERISPOMENI
|
||||
U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1fc1, // U+1FC1 GREEK DIALYTIKA AND PERISPOMENI
|
||||
U_GREEK_PSILI_AND_VARIA = 0x1fcd, // U+1FCD GREEK PSILI AND VARIA
|
||||
U_GREEK_PSILI_AND_OXIA = 0x1fce, // U+1FCE GREEK PSILI AND OXIA
|
||||
U_GREEK_PSILI_AND_PERISPOMENI = 0x1fcf, // U+1FCF GREEK PSILI AND PERISPOMENI
|
||||
U_GREEK_DASIA_AND_VARIA = 0x1fdd, // U+1FDD GREEK DASIA AND VARIA
|
||||
U_GREEK_DASIA_AND_OXIA = 0x1fde, // U+1FDE GREEK DASIA AND OXIA
|
||||
U_GREEK_DASIA_AND_PERISPOMENI = 0x1fdf, // U+1FDF GREEK DASIA AND PERISPOMENI
|
||||
U_GREEK_DIALYTIKA_AND_VARIA = 0x1fed, // U+1FED GREEK DIALYTIKA AND VARIA
|
||||
U_GREEK_DIALYTIKA_AND_OXIA = 0x1fee, // U+1FEE GREEK DIALYTIKA AND OXIA
|
||||
U_GREEK_VARIA = 0x1fef, // U+1FEF GREEK VARIA
|
||||
U_GREEK_OXIA = 0x1ffd, // U+1FFD GREEK OXIA
|
||||
U_GREEK_DASIA = 0x1ffe, // U+1FFE GREEK DASIA
|
||||
|
||||
U_OVERLINE = 0x203e, // Unicode Character 'OVERLINE'
|
||||
|
||||
/**
|
||||
* UTF-8 BOM
|
||||
* Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF)
|
||||
* http://www.fileformat.info/info/unicode/char/feff/index.htm
|
||||
*/
|
||||
UTF8_BOM = 65279,
|
||||
}
|
||||
197
packages/foam-core/src/common/platform.ts
Normal file
197
packages/foam-core/src/common/platform.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
|
||||
const LANGUAGE_DEFAULT = 'en';
|
||||
|
||||
let _isWindows = false;
|
||||
let _isMacintosh = false;
|
||||
let _isLinux = false;
|
||||
let _isNative = false;
|
||||
let _isWeb = false;
|
||||
let _isIOS = false;
|
||||
let _locale: string | undefined = undefined;
|
||||
let _language: string = LANGUAGE_DEFAULT;
|
||||
let _translationsConfigFile: string | undefined = undefined;
|
||||
let _userAgent: string | undefined = undefined;
|
||||
|
||||
interface NLSConfig {
|
||||
locale: string;
|
||||
availableLanguages: { [key: string]: string };
|
||||
_translationsConfigFile: string;
|
||||
}
|
||||
|
||||
export interface IProcessEnvironment {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface INodeProcess {
|
||||
platform: 'win32' | 'linux' | 'darwin';
|
||||
env: IProcessEnvironment;
|
||||
nextTick: Function;
|
||||
versions?: {
|
||||
electron?: string;
|
||||
};
|
||||
sandboxed?: boolean; // Electron
|
||||
type?: string;
|
||||
cwd(): string;
|
||||
}
|
||||
declare const process: INodeProcess;
|
||||
declare const global: any;
|
||||
|
||||
interface INavigator {
|
||||
userAgent: string;
|
||||
language: string;
|
||||
maxTouchPoints?: number;
|
||||
}
|
||||
declare const navigator: INavigator;
|
||||
declare const self: any;
|
||||
|
||||
const _globals =
|
||||
typeof self === 'object'
|
||||
? self
|
||||
: typeof global === 'object'
|
||||
? global
|
||||
: ({} as any);
|
||||
|
||||
let nodeProcess: INodeProcess | undefined = undefined;
|
||||
if (typeof process !== 'undefined') {
|
||||
// Native environment (non-sandboxed)
|
||||
nodeProcess = process;
|
||||
} else if (typeof _globals.vscode !== 'undefined') {
|
||||
// Native environment (sandboxed)
|
||||
nodeProcess = _globals.vscode.process;
|
||||
}
|
||||
|
||||
const isElectronRenderer =
|
||||
typeof nodeProcess?.versions?.electron === 'string' &&
|
||||
nodeProcess.type === 'renderer';
|
||||
export const isElectronSandboxed = isElectronRenderer && nodeProcess?.sandboxed;
|
||||
|
||||
// Web environment
|
||||
if (typeof navigator === 'object' && !isElectronRenderer) {
|
||||
_userAgent = navigator.userAgent;
|
||||
_isWindows = _userAgent.indexOf('Windows') >= 0;
|
||||
_isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
|
||||
_isIOS =
|
||||
(_userAgent.indexOf('Macintosh') >= 0 ||
|
||||
_userAgent.indexOf('iPad') >= 0 ||
|
||||
_userAgent.indexOf('iPhone') >= 0) &&
|
||||
!!navigator.maxTouchPoints &&
|
||||
navigator.maxTouchPoints > 0;
|
||||
_isLinux = _userAgent.indexOf('Linux') >= 0;
|
||||
_isWeb = true;
|
||||
_locale = navigator.language;
|
||||
_language = _locale;
|
||||
}
|
||||
|
||||
// Native environment
|
||||
else if (typeof nodeProcess === 'object') {
|
||||
_isWindows = nodeProcess.platform === 'win32';
|
||||
_isMacintosh = nodeProcess.platform === 'darwin';
|
||||
_isLinux = nodeProcess.platform === 'linux';
|
||||
_locale = LANGUAGE_DEFAULT;
|
||||
_language = LANGUAGE_DEFAULT;
|
||||
const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG'];
|
||||
if (rawNlsConfig) {
|
||||
try {
|
||||
const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
|
||||
const resolved = nlsConfig.availableLanguages['*'];
|
||||
_locale = nlsConfig.locale;
|
||||
// VSCode's default language is 'en'
|
||||
_language = resolved ? resolved : LANGUAGE_DEFAULT;
|
||||
_translationsConfigFile = nlsConfig._translationsConfigFile;
|
||||
} catch (e) {}
|
||||
}
|
||||
_isNative = true;
|
||||
}
|
||||
|
||||
// Unknown environment
|
||||
else {
|
||||
console.error('Unable to resolve platform.');
|
||||
}
|
||||
|
||||
export const enum Platform {
|
||||
Web,
|
||||
Mac,
|
||||
Linux,
|
||||
Windows,
|
||||
}
|
||||
export function PlatformToString(platform: Platform) {
|
||||
switch (platform) {
|
||||
case Platform.Web:
|
||||
return 'Web';
|
||||
case Platform.Mac:
|
||||
return 'Mac';
|
||||
case Platform.Linux:
|
||||
return 'Linux';
|
||||
case Platform.Windows:
|
||||
return 'Windows';
|
||||
}
|
||||
}
|
||||
|
||||
let _platform: Platform = Platform.Web;
|
||||
if (_isMacintosh) {
|
||||
_platform = Platform.Mac;
|
||||
} else if (_isWindows) {
|
||||
_platform = Platform.Windows;
|
||||
} else if (_isLinux) {
|
||||
_platform = Platform.Linux;
|
||||
}
|
||||
|
||||
export const isWindows = _isWindows;
|
||||
export const isMacintosh = _isMacintosh;
|
||||
export const isLinux = _isLinux;
|
||||
export const isNative = _isNative;
|
||||
export const isWeb = _isWeb;
|
||||
export const isIOS = _isIOS;
|
||||
export const platform = _platform;
|
||||
export const userAgent = _userAgent;
|
||||
|
||||
/**
|
||||
* The language used for the user interface. The format of
|
||||
* the string is all lower case (e.g. zh-tw for Traditional
|
||||
* Chinese)
|
||||
*/
|
||||
export const language = _language;
|
||||
|
||||
export namespace Language {
|
||||
export function value(): string {
|
||||
return language;
|
||||
}
|
||||
|
||||
export function isDefaultVariant(): boolean {
|
||||
if (language.length === 2) {
|
||||
return language === 'en';
|
||||
} else if (language.length >= 3) {
|
||||
return language[0] === 'e' && language[1] === 'n' && language[2] === '-';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isDefault(): boolean {
|
||||
return language === 'en';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The OS locale or the locale specified by --locale. The format of
|
||||
* the string is all lower case (e.g. zh-tw for Traditional
|
||||
* Chinese). The UI is not necessarily shown in the provided locale.
|
||||
*/
|
||||
export const locale = _locale;
|
||||
|
||||
/**
|
||||
* The translatios that are available through language packs.
|
||||
*/
|
||||
export const translationsConfigFile = _translationsConfigFile;
|
||||
|
||||
export const globals: any = _globals;
|
||||
|
||||
interface ISetImmediate {
|
||||
(callback: (...args: any[]) => void): void;
|
||||
}
|
||||
748
packages/foam-core/src/common/uri.ts
Normal file
748
packages/foam-core/src/common/uri.ts
Normal file
@@ -0,0 +1,748 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// taken from https://github.com/microsoft/vscode/tree/master/src/vs/base/common
|
||||
|
||||
import { isWindows } from './platform';
|
||||
import { CharCode } from './charCode';
|
||||
import * as paths from 'path';
|
||||
|
||||
const _schemePattern = /^\w[\w\d+.-]*$/;
|
||||
const _singleSlashStart = /^\//;
|
||||
const _doubleSlashStart = /^\/\//;
|
||||
|
||||
function _validateUri(ret: URI, _strict?: boolean): void {
|
||||
// scheme, must be set
|
||||
if (!ret.scheme && _strict) {
|
||||
throw new Error(
|
||||
`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`
|
||||
);
|
||||
}
|
||||
|
||||
// scheme, https://tools.ietf.org/html/rfc3986#section-3.1
|
||||
// ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
||||
if (ret.scheme && !_schemePattern.test(ret.scheme)) {
|
||||
throw new Error('[UriError]: Scheme contains illegal characters.');
|
||||
}
|
||||
|
||||
// path, http://tools.ietf.org/html/rfc3986#section-3.3
|
||||
// If a URI contains an authority component, then the path component
|
||||
// must either be empty or begin with a slash ("/") character. If a URI
|
||||
// does not contain an authority component, then the path cannot begin
|
||||
// with two slash characters ("//").
|
||||
if (ret.path) {
|
||||
if (ret.authority) {
|
||||
if (!_singleSlashStart.test(ret.path)) {
|
||||
throw new Error(
|
||||
'[UriError]: If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (_doubleSlashStart.test(ret.path)) {
|
||||
throw new Error(
|
||||
'[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for a while we allowed uris *without* schemes and this is the migration
|
||||
// for them, e.g. an uri without scheme and without strict-mode warns and falls
|
||||
// back to the file-scheme. that should cause the least carnage and still be a
|
||||
// clear warning
|
||||
function _schemeFix(scheme: string, _strict: boolean): string {
|
||||
if (!scheme && !_strict) {
|
||||
return 'file';
|
||||
}
|
||||
return scheme;
|
||||
}
|
||||
|
||||
// implements a bit of https://tools.ietf.org/html/rfc3986#section-5
|
||||
function _referenceResolution(scheme: string, path: string): string {
|
||||
// the slash-character is our 'default base' as we don't
|
||||
// support constructing URIs relative to other URIs. This
|
||||
// also means that we alter and potentially break paths.
|
||||
// see https://tools.ietf.org/html/rfc3986#section-5.1.4
|
||||
switch (scheme) {
|
||||
case 'https':
|
||||
case 'http':
|
||||
case 'file':
|
||||
if (!path) {
|
||||
path = _slash;
|
||||
} else if (path[0] !== _slash) {
|
||||
path = _slash + path;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
const _empty = '';
|
||||
const _slash = '/';
|
||||
const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
||||
|
||||
/**
|
||||
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
|
||||
* This class is a simple parser which creates the basic component parts
|
||||
* (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
|
||||
* and encoding.
|
||||
*
|
||||
* ```txt
|
||||
* foo://example.com:8042/over/there?name=ferret#nose
|
||||
* \_/ \______________/\_________/ \_________/ \__/
|
||||
* | | | | |
|
||||
* scheme authority path query fragment
|
||||
* | _____________________|__
|
||||
* / \ / \
|
||||
* urn:example:animal:ferret:nose
|
||||
* ```
|
||||
*/
|
||||
export class URI implements UriComponents {
|
||||
static isUri(thing: any): thing is URI {
|
||||
if (thing instanceof URI) {
|
||||
return true;
|
||||
}
|
||||
if (!thing) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
typeof (thing as URI).authority === 'string' &&
|
||||
typeof (thing as URI).fragment === 'string' &&
|
||||
typeof (thing as URI).path === 'string' &&
|
||||
typeof (thing as URI).query === 'string' &&
|
||||
typeof (thing as URI).scheme === 'string' &&
|
||||
typeof (thing as URI).fsPath === 'function' &&
|
||||
typeof (thing as URI).with === 'function' &&
|
||||
typeof (thing as URI).toString === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
* The part before the first colon.
|
||||
*/
|
||||
readonly scheme: string;
|
||||
|
||||
/**
|
||||
* authority is the 'www.msft.com' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
* The part between the first double slashes and the next slash.
|
||||
*/
|
||||
readonly authority: string;
|
||||
|
||||
/**
|
||||
* path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
readonly path: string;
|
||||
|
||||
/**
|
||||
* query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
readonly query: string;
|
||||
|
||||
/**
|
||||
* fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
readonly fragment: string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected constructor(
|
||||
scheme: string,
|
||||
authority?: string,
|
||||
path?: string,
|
||||
query?: string,
|
||||
fragment?: string,
|
||||
_strict?: boolean
|
||||
);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected constructor(components: UriComponents);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected constructor(
|
||||
schemeOrData: string | UriComponents,
|
||||
authority?: string,
|
||||
path?: string,
|
||||
query?: string,
|
||||
fragment?: string,
|
||||
_strict: boolean = false
|
||||
) {
|
||||
if (typeof schemeOrData === 'object') {
|
||||
this.scheme = schemeOrData.scheme || _empty;
|
||||
this.authority = schemeOrData.authority || _empty;
|
||||
this.path = schemeOrData.path || _empty;
|
||||
this.query = schemeOrData.query || _empty;
|
||||
this.fragment = schemeOrData.fragment || _empty;
|
||||
// no validation because it's this URI
|
||||
// that creates uri components.
|
||||
// _validateUri(this);
|
||||
} else {
|
||||
this.scheme = _schemeFix(schemeOrData, _strict);
|
||||
this.authority = authority || _empty;
|
||||
this.path = _referenceResolution(this.scheme, path || _empty);
|
||||
this.query = query || _empty;
|
||||
this.fragment = fragment || _empty;
|
||||
|
||||
_validateUri(this, _strict);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- filesystem path -----------------------
|
||||
|
||||
/**
|
||||
* Returns a string representing the corresponding file system path of this URI.
|
||||
* Will handle UNC paths, normalizes windows drive letters to lower-case, and uses the
|
||||
* platform specific path separator.
|
||||
*
|
||||
* * Will *not* validate the path for invalid characters and semantics.
|
||||
* * Will *not* look at the scheme of this URI.
|
||||
* * The result shall *not* be used for display purposes but for accessing a file on disk.
|
||||
*
|
||||
*
|
||||
* The *difference* to `URI#path` is the use of the platform specific separator and the handling
|
||||
* of UNC paths. See the below sample of a file-uri with an authority (UNC path).
|
||||
*
|
||||
* ```ts
|
||||
const u = URI.parse('file://server/c$/folder/file.txt')
|
||||
u.authority === 'server'
|
||||
u.path === '/shares/c$/file.txt'
|
||||
u.fsPath === '\\server\c$\folder\file.txt'
|
||||
```
|
||||
*
|
||||
* Using `URI#path` to read a file (using fs-apis) would not be enough because parts of the path,
|
||||
* namely the server name, would be missing. Therefore `URI#fsPath` exists - it's sugar to ease working
|
||||
* with URIs that represent files on disk (`file` scheme).
|
||||
*/
|
||||
get fsPath(): string {
|
||||
// if (this.scheme !== 'file') {
|
||||
// console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`);
|
||||
// }
|
||||
return uriToFsPath(this, false);
|
||||
}
|
||||
|
||||
// ---- modify to new -------------------------
|
||||
|
||||
with(change: {
|
||||
scheme?: string;
|
||||
authority?: string | null;
|
||||
path?: string | null;
|
||||
query?: string | null;
|
||||
fragment?: string | null;
|
||||
}): URI {
|
||||
if (!change) {
|
||||
return this;
|
||||
}
|
||||
|
||||
let { scheme, authority, path, query, fragment } = change;
|
||||
if (scheme === undefined) {
|
||||
scheme = this.scheme;
|
||||
} else if (scheme === null) {
|
||||
scheme = _empty;
|
||||
}
|
||||
if (authority === undefined) {
|
||||
authority = this.authority;
|
||||
} else if (authority === null) {
|
||||
authority = _empty;
|
||||
}
|
||||
if (path === undefined) {
|
||||
path = this.path;
|
||||
} else if (path === null) {
|
||||
path = _empty;
|
||||
}
|
||||
if (query === undefined) {
|
||||
query = this.query;
|
||||
} else if (query === null) {
|
||||
query = _empty;
|
||||
}
|
||||
if (fragment === undefined) {
|
||||
fragment = this.fragment;
|
||||
} else if (fragment === null) {
|
||||
fragment = _empty;
|
||||
}
|
||||
|
||||
if (
|
||||
scheme === this.scheme &&
|
||||
authority === this.authority &&
|
||||
path === this.path &&
|
||||
query === this.query &&
|
||||
fragment === this.fragment
|
||||
) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return new Uri(scheme, authority, path, query, fragment);
|
||||
}
|
||||
|
||||
// ---- parse & validate ------------------------
|
||||
|
||||
/**
|
||||
* Creates a new URI from a string, e.g. `http://www.msft.com/some/path`,
|
||||
* `file:///usr/home`, or `scheme:with/path`.
|
||||
*
|
||||
* @param value A string which represents an URI (see `URI#toString`).
|
||||
*/
|
||||
static parse(value: string, _strict: boolean = false): URI {
|
||||
const match = _regexp.exec(value);
|
||||
if (!match) {
|
||||
return new Uri(_empty, _empty, _empty, _empty, _empty);
|
||||
}
|
||||
return new Uri(
|
||||
match[2] || _empty,
|
||||
percentDecode(match[4] || _empty),
|
||||
percentDecode(match[5] || _empty),
|
||||
percentDecode(match[7] || _empty),
|
||||
percentDecode(match[9] || _empty),
|
||||
_strict
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new URI from a file system path, e.g. `c:\my\files`,
|
||||
* `/usr/home`, or `\\server\share\some\path`.
|
||||
*
|
||||
* The *difference* between `URI#parse` and `URI#file` is that the latter treats the argument
|
||||
* as path, not as stringified-uri. E.g. `URI.file(path)` is **not the same as**
|
||||
* `URI.parse('file://' + path)` because the path might contain characters that are
|
||||
* interpreted (# and ?). See the following sample:
|
||||
* ```ts
|
||||
const good = URI.file('/coding/c#/project1');
|
||||
good.scheme === 'file';
|
||||
good.path === '/coding/c#/project1';
|
||||
good.fragment === '';
|
||||
const bad = URI.parse('file://' + '/coding/c#/project1');
|
||||
bad.scheme === 'file';
|
||||
bad.path === '/coding/c'; // path is now broken
|
||||
bad.fragment === '/project1';
|
||||
```
|
||||
*
|
||||
* @param path A file system path (see `URI#fsPath`)
|
||||
*/
|
||||
static file(path: string): URI {
|
||||
let authority = _empty;
|
||||
|
||||
// normalize to fwd-slashes on windows,
|
||||
// on other systems bwd-slashes are valid
|
||||
// filename character, eg /f\oo/ba\r.txt
|
||||
if (isWindows) {
|
||||
path = path.replace(/\\/g, _slash);
|
||||
}
|
||||
|
||||
// check for authority as used in UNC shares
|
||||
// or use the path as given
|
||||
if (path[0] === _slash && path[1] === _slash) {
|
||||
const idx = path.indexOf(_slash, 2);
|
||||
if (idx === -1) {
|
||||
authority = path.substring(2);
|
||||
path = _slash;
|
||||
} else {
|
||||
authority = path.substring(2, idx);
|
||||
path = path.substring(idx) || _slash;
|
||||
}
|
||||
}
|
||||
|
||||
return new Uri('file', authority, path, _empty, _empty);
|
||||
}
|
||||
|
||||
static from(components: {
|
||||
scheme: string;
|
||||
authority?: string;
|
||||
path?: string;
|
||||
query?: string;
|
||||
fragment?: string;
|
||||
}): URI {
|
||||
return new Uri(
|
||||
components.scheme,
|
||||
components.authority,
|
||||
components.path,
|
||||
components.query,
|
||||
components.fragment
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a URI path with path fragments and normalizes the resulting path.
|
||||
*
|
||||
* @param uri The input URI.
|
||||
* @param pathFragment The path fragment to add to the URI path.
|
||||
* @returns The resulting URI.
|
||||
*/
|
||||
static joinPath(uri: URI, ...pathFragment: string[]): URI {
|
||||
if (!uri.path) {
|
||||
throw new Error(`[UriError]: cannot call joinPath on URI without path`);
|
||||
}
|
||||
let newPath: string;
|
||||
if (isWindows && uri.scheme === 'file') {
|
||||
newPath = URI.file(
|
||||
paths.win32.join(uriToFsPath(uri, true), ...pathFragment)
|
||||
).path;
|
||||
} else {
|
||||
newPath = paths.posix.join(uri.path, ...pathFragment);
|
||||
}
|
||||
return uri.with({ path: newPath });
|
||||
}
|
||||
|
||||
// ---- printing/externalize ---------------------------
|
||||
|
||||
/**
|
||||
* Creates a string representation for this URI. It's guaranteed that calling
|
||||
* `URI.parse` with the result of this function creates an URI which is equal
|
||||
* to this URI.
|
||||
*
|
||||
* * The result shall *not* be used for display purposes but for externalization or transport.
|
||||
* * The result will be encoded using the percentage encoding and encoding happens mostly
|
||||
* ignore the scheme-specific encoding rules.
|
||||
*
|
||||
* @param skipEncoding Do not encode the result, default is `false`
|
||||
*/
|
||||
toString(skipEncoding: boolean = false): string {
|
||||
return _asFormatted(this, skipEncoding);
|
||||
}
|
||||
|
||||
toJSON(): UriComponents {
|
||||
return this;
|
||||
}
|
||||
|
||||
static revive(data: UriComponents | URI): URI;
|
||||
static revive(data: UriComponents | URI | undefined): URI | undefined;
|
||||
static revive(data: UriComponents | URI | null): URI | null;
|
||||
static revive(
|
||||
data: UriComponents | URI | undefined | null
|
||||
): URI | undefined | null;
|
||||
static revive(
|
||||
data: UriComponents | URI | undefined | null
|
||||
): URI | undefined | null {
|
||||
if (!data) {
|
||||
return data;
|
||||
} else if (data instanceof URI) {
|
||||
return data;
|
||||
} else {
|
||||
const result = new Uri(data);
|
||||
result._formatted = (data as UriState).external;
|
||||
result._fsPath =
|
||||
(data as UriState)._sep === _pathSepMarker
|
||||
? (data as UriState).fsPath
|
||||
: null;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface UriComponents {
|
||||
scheme: string;
|
||||
authority: string;
|
||||
path: string;
|
||||
query: string;
|
||||
fragment: string;
|
||||
}
|
||||
|
||||
interface UriState extends UriComponents {
|
||||
$mid: number;
|
||||
external: string;
|
||||
fsPath: string;
|
||||
_sep: 1 | undefined;
|
||||
}
|
||||
|
||||
const _pathSepMarker = isWindows ? 1 : undefined;
|
||||
|
||||
// This class exists so that URI is compatibile with vscode.Uri (API).
|
||||
class Uri extends URI {
|
||||
_formatted: string | null = null;
|
||||
_fsPath: string | null = null;
|
||||
|
||||
get fsPath(): string {
|
||||
if (!this._fsPath) {
|
||||
this._fsPath = uriToFsPath(this, false);
|
||||
}
|
||||
return this._fsPath;
|
||||
}
|
||||
|
||||
toString(skipEncoding: boolean = false): string {
|
||||
if (!skipEncoding) {
|
||||
if (!this._formatted) {
|
||||
this._formatted = _asFormatted(this, false);
|
||||
}
|
||||
return this._formatted;
|
||||
} else {
|
||||
// we don't cache that
|
||||
return _asFormatted(this, true);
|
||||
}
|
||||
}
|
||||
|
||||
toJSON(): UriComponents {
|
||||
const res = {
|
||||
$mid: 1,
|
||||
} as UriState;
|
||||
// cached state
|
||||
if (this._fsPath) {
|
||||
res.fsPath = this._fsPath;
|
||||
res._sep = _pathSepMarker;
|
||||
}
|
||||
if (this._formatted) {
|
||||
res.external = this._formatted;
|
||||
}
|
||||
// uri components
|
||||
if (this.path) {
|
||||
res.path = this.path;
|
||||
}
|
||||
if (this.scheme) {
|
||||
res.scheme = this.scheme;
|
||||
}
|
||||
if (this.authority) {
|
||||
res.authority = this.authority;
|
||||
}
|
||||
if (this.query) {
|
||||
res.query = this.query;
|
||||
}
|
||||
if (this.fragment) {
|
||||
res.fragment = this.fragment;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2
|
||||
const encodeTable: { [ch: number]: string } = {
|
||||
[CharCode.Colon]: '%3A', // gen-delims
|
||||
[CharCode.Slash]: '%2F',
|
||||
[CharCode.QuestionMark]: '%3F',
|
||||
[CharCode.Hash]: '%23',
|
||||
[CharCode.OpenSquareBracket]: '%5B',
|
||||
[CharCode.CloseSquareBracket]: '%5D',
|
||||
[CharCode.AtSign]: '%40',
|
||||
|
||||
[CharCode.ExclamationMark]: '%21', // sub-delims
|
||||
[CharCode.DollarSign]: '%24',
|
||||
[CharCode.Ampersand]: '%26',
|
||||
[CharCode.SingleQuote]: '%27',
|
||||
[CharCode.OpenParen]: '%28',
|
||||
[CharCode.CloseParen]: '%29',
|
||||
[CharCode.Asterisk]: '%2A',
|
||||
[CharCode.Plus]: '%2B',
|
||||
[CharCode.Comma]: '%2C',
|
||||
[CharCode.Semicolon]: '%3B',
|
||||
[CharCode.Equals]: '%3D',
|
||||
|
||||
[CharCode.Space]: '%20',
|
||||
};
|
||||
|
||||
function encodeURIComponentFast(
|
||||
uriComponent: string,
|
||||
allowSlash: boolean
|
||||
): string {
|
||||
let res: string | undefined = undefined;
|
||||
let nativeEncodePos = -1;
|
||||
|
||||
for (let pos = 0; pos < uriComponent.length; pos++) {
|
||||
const code = uriComponent.charCodeAt(pos);
|
||||
|
||||
// unreserved characters: https://tools.ietf.org/html/rfc3986#section-2.3
|
||||
if (
|
||||
(code >= CharCode.a && code <= CharCode.z) ||
|
||||
(code >= CharCode.A && code <= CharCode.Z) ||
|
||||
(code >= CharCode.Digit0 && code <= CharCode.Digit9) ||
|
||||
code === CharCode.Dash ||
|
||||
code === CharCode.Period ||
|
||||
code === CharCode.Underline ||
|
||||
code === CharCode.Tilde ||
|
||||
(allowSlash && code === CharCode.Slash)
|
||||
) {
|
||||
// check if we are delaying native encode
|
||||
if (nativeEncodePos !== -1) {
|
||||
res += encodeURIComponent(uriComponent.substring(nativeEncodePos, pos));
|
||||
nativeEncodePos = -1;
|
||||
}
|
||||
// check if we write into a new string (by default we try to return the param)
|
||||
if (res !== undefined) {
|
||||
res += uriComponent.charAt(pos);
|
||||
}
|
||||
} else {
|
||||
// encoding needed, we need to allocate a new string
|
||||
if (res === undefined) {
|
||||
res = uriComponent.substr(0, pos);
|
||||
}
|
||||
|
||||
// check with default table first
|
||||
const escaped = encodeTable[code];
|
||||
if (escaped !== undefined) {
|
||||
// check if we are delaying native encode
|
||||
if (nativeEncodePos !== -1) {
|
||||
res += encodeURIComponent(
|
||||
uriComponent.substring(nativeEncodePos, pos)
|
||||
);
|
||||
nativeEncodePos = -1;
|
||||
}
|
||||
|
||||
// append escaped variant to result
|
||||
res += escaped;
|
||||
} else if (nativeEncodePos === -1) {
|
||||
// use native encode only when needed
|
||||
nativeEncodePos = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nativeEncodePos !== -1) {
|
||||
res += encodeURIComponent(uriComponent.substring(nativeEncodePos));
|
||||
}
|
||||
|
||||
return res !== undefined ? res : uriComponent;
|
||||
}
|
||||
|
||||
function encodeURIComponentMinimal(path: string): string {
|
||||
let res: string | undefined = undefined;
|
||||
for (let pos = 0; pos < path.length; pos++) {
|
||||
const code = path.charCodeAt(pos);
|
||||
if (code === CharCode.Hash || code === CharCode.QuestionMark) {
|
||||
if (res === undefined) {
|
||||
res = path.substr(0, pos);
|
||||
}
|
||||
res += encodeTable[code];
|
||||
} else {
|
||||
if (res !== undefined) {
|
||||
res += path[pos];
|
||||
}
|
||||
}
|
||||
}
|
||||
return res !== undefined ? res : path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute `fsPath` for the given uri
|
||||
*/
|
||||
export function uriToFsPath(uri: URI, keepDriveLetterCasing: boolean): string {
|
||||
let value: string;
|
||||
if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') {
|
||||
// unc path: file://shares/c$/far/boo
|
||||
value = `//${uri.authority}${uri.path}`;
|
||||
} else if (
|
||||
uri.path.charCodeAt(0) === CharCode.Slash &&
|
||||
((uri.path.charCodeAt(1) >= CharCode.A &&
|
||||
uri.path.charCodeAt(1) <= CharCode.Z) ||
|
||||
(uri.path.charCodeAt(1) >= CharCode.a &&
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
// other path
|
||||
value = uri.path;
|
||||
}
|
||||
if (isWindows) {
|
||||
value = value.replace(/\//g, '\\');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the external version of a uri
|
||||
*/
|
||||
function _asFormatted(uri: URI, skipEncoding: boolean): string {
|
||||
const encoder = !skipEncoding
|
||||
? encodeURIComponentFast
|
||||
: encodeURIComponentMinimal;
|
||||
|
||||
let res = '';
|
||||
let { scheme, authority, path, query, fragment } = uri;
|
||||
if (scheme) {
|
||||
res += scheme;
|
||||
res += ':';
|
||||
}
|
||||
if (authority || scheme === 'file') {
|
||||
res += _slash;
|
||||
res += _slash;
|
||||
}
|
||||
if (authority) {
|
||||
let idx = authority.indexOf('@');
|
||||
if (idx !== -1) {
|
||||
// <user>@<auth>
|
||||
const userinfo = authority.substr(0, idx);
|
||||
authority = authority.substr(idx + 1);
|
||||
idx = userinfo.indexOf(':');
|
||||
if (idx === -1) {
|
||||
res += encoder(userinfo, false);
|
||||
} else {
|
||||
// <user>:<pass>@<auth>
|
||||
res += encoder(userinfo.substr(0, idx), false);
|
||||
res += ':';
|
||||
res += encoder(userinfo.substr(idx + 1), false);
|
||||
}
|
||||
res += '@';
|
||||
}
|
||||
authority = authority.toLowerCase();
|
||||
idx = authority.indexOf(':');
|
||||
if (idx === -1) {
|
||||
res += encoder(authority, false);
|
||||
} else {
|
||||
// <auth>:<port>
|
||||
res += encoder(authority.substr(0, idx), false);
|
||||
res += authority.substr(idx);
|
||||
}
|
||||
}
|
||||
if (path) {
|
||||
// lower-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
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
// encode the rest of the path
|
||||
res += encoder(path, true);
|
||||
}
|
||||
if (query) {
|
||||
res += '?';
|
||||
res += encoder(query, false);
|
||||
}
|
||||
if (fragment) {
|
||||
res += '#';
|
||||
res += !skipEncoding ? encodeURIComponentFast(fragment, false) : fragment;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// --- decode
|
||||
|
||||
function decodeURIComponentGraceful(str: string): string {
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch {
|
||||
if (str.length > 3) {
|
||||
return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3));
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g;
|
||||
|
||||
function percentDecode(str: string): string {
|
||||
if (!str.match(_rEncodedAsHex)) {
|
||||
return str;
|
||||
}
|
||||
return str.replace(_rEncodedAsHex, match =>
|
||||
decodeURIComponentGraceful(match)
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { merge } from 'lodash';
|
||||
import { Logger } from './utils/log';
|
||||
import { URI } from './common/uri';
|
||||
|
||||
export interface FoamConfig {
|
||||
workspaceFolders: string[];
|
||||
workspaceFolders: URI[];
|
||||
includeGlobs: string[];
|
||||
ignoreGlobs: string[];
|
||||
get<T>(path: string): T | undefined;
|
||||
@@ -14,7 +16,7 @@ const DEFAULT_INCLUDES = ['**/*'];
|
||||
const DEFAULT_IGNORES = ['**/node_modules/**'];
|
||||
|
||||
export const createConfigFromObject = (
|
||||
workspaceFolders: string[],
|
||||
workspaceFolders: URI[],
|
||||
include: string[],
|
||||
ignore: string[],
|
||||
settings: any
|
||||
@@ -33,7 +35,7 @@ export const createConfigFromObject = (
|
||||
};
|
||||
|
||||
export const createConfigFromFolders = (
|
||||
workspaceFolders: string[] | string,
|
||||
workspaceFolders: URI[] | URI,
|
||||
options: {
|
||||
include?: string[];
|
||||
ignore?: string[];
|
||||
@@ -43,7 +45,7 @@ export const createConfigFromFolders = (
|
||||
workspaceFolders = [workspaceFolders];
|
||||
}
|
||||
const workspaceConfig: any = workspaceFolders.reduce(
|
||||
(acc, f) => merge(acc, parseConfig(`${f}/config.json`)),
|
||||
(acc, f) => merge(acc, parseConfig(URI.joinPath(f, 'config.json'))),
|
||||
{}
|
||||
);
|
||||
// For security reasons local plugins can only be
|
||||
@@ -52,7 +54,7 @@ export const createConfigFromFolders = (
|
||||
delete workspaceConfig['experimental']['localPlugins'];
|
||||
}
|
||||
|
||||
const userConfig = parseConfig(`~/.foam/config.json`);
|
||||
const userConfig = parseConfig(URI.file(`~/.foam/config.json`));
|
||||
|
||||
const settings = merge(workspaceConfig, userConfig);
|
||||
|
||||
@@ -64,10 +66,10 @@ export const createConfigFromFolders = (
|
||||
);
|
||||
};
|
||||
|
||||
const parseConfig = (path: string) => {
|
||||
const parseConfig = (path: URI) => {
|
||||
try {
|
||||
return JSON.parse(readFileSync(path, 'utf8'));
|
||||
return JSON.parse(readFileSync(path.fsPath, 'utf8'));
|
||||
} catch {
|
||||
console.warn('Could not read configuration from ' + path);
|
||||
Logger.debug('Could not read configuration from ' + path);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { Note, NoteLink, URI } from './types';
|
||||
import { Note, NoteLink } from './types';
|
||||
import { URI } from './common/uri';
|
||||
import { NoteGraph, NoteGraphAPI } from './note-graph';
|
||||
import { FoamConfig } from './config';
|
||||
import { IDataStore, FileDataStore } from './services/datastore';
|
||||
import { ILogger } from './utils/log';
|
||||
import { IDisposable, isDisposable } from './common/lifecycle';
|
||||
|
||||
export { IDataStore, FileDataStore };
|
||||
export { ILogger };
|
||||
export { LogLevel, LogLevelThreshold, Logger, BaseLogger } from './utils/log';
|
||||
export { IDisposable, isDisposable } from './common/lifecycle';
|
||||
export { Event, Emitter } from './common/event';
|
||||
export { FoamConfig };
|
||||
|
||||
export { IDisposable, isDisposable };
|
||||
|
||||
export {
|
||||
createMarkdownReferences,
|
||||
stringifyMarkdownLinkReferenceDefinition,
|
||||
@@ -40,7 +43,7 @@ export interface Services {
|
||||
dataStore: IDataStore;
|
||||
}
|
||||
|
||||
export interface Foam {
|
||||
export interface Foam extends IDisposable {
|
||||
notes: NoteGraphAPI;
|
||||
config: FoamConfig;
|
||||
parse: (uri: URI, text: string, eol: string) => Note;
|
||||
|
||||
@@ -7,18 +7,14 @@ import visit from 'unist-util-visit';
|
||||
import { Parent, Point } from 'unist';
|
||||
import detectNewline from 'detect-newline';
|
||||
import os from 'os';
|
||||
import * as path from 'path';
|
||||
import { NoteGraphAPI } from './note-graph';
|
||||
import { NoteLinkDefinition, Note, NoteParser } from './types';
|
||||
import {
|
||||
dropExtension,
|
||||
uriToSlug,
|
||||
extractHashtags,
|
||||
extractTagsFromProp,
|
||||
} from './utils';
|
||||
import { dropExtension, extractHashtags, extractTagsFromProp } from './utils';
|
||||
import { uriToSlug, computeRelativePath, getBasename } from './utils/uri';
|
||||
import { ID } from './types';
|
||||
import { ParserPlugin } from './plugins';
|
||||
import { Logger } from './utils/log';
|
||||
import { URI } from './common/uri';
|
||||
|
||||
const tagsPlugin: ParserPlugin = {
|
||||
name: 'tags',
|
||||
@@ -45,7 +41,7 @@ const titlePlugin: ParserPlugin = {
|
||||
},
|
||||
onDidVisitTree: (tree, note) => {
|
||||
if (note.title == null) {
|
||||
note.title = path.parse(note.source.uri).name;
|
||||
note.title = getBasename(note.source.uri);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -83,12 +79,12 @@ const definitionsPlugin: ParserPlugin = {
|
||||
const handleError = (
|
||||
plugin: ParserPlugin,
|
||||
fnName: string,
|
||||
uri: string | undefined,
|
||||
uri: URI | undefined,
|
||||
e: Error
|
||||
): void => {
|
||||
const name = plugin.name || '';
|
||||
Logger.warn(
|
||||
`Error while executing [${fnName}] in plugin [${name}] for file [${uri}]`,
|
||||
`Error while executing [${fnName}] in plugin [${name}] for file [${uri?.path}]`,
|
||||
e
|
||||
);
|
||||
};
|
||||
@@ -116,7 +112,7 @@ export function createMarkdownParser(extraPlugins: ParserPlugin[]): NoteParser {
|
||||
});
|
||||
|
||||
const foamParser: NoteParser = {
|
||||
parse: (uri: string, markdown: string): Note => {
|
||||
parse: (uri: URI, markdown: string): Note => {
|
||||
Logger.debug('Parsing:', uri);
|
||||
markdown = plugins.reduce((acc, plugin) => {
|
||||
try {
|
||||
@@ -277,8 +273,8 @@ export function createMarkdownReferences(
|
||||
return null;
|
||||
}
|
||||
|
||||
const relativePath = path.relative(
|
||||
path.dirname(source.source.uri),
|
||||
const relativePath = computeRelativePath(
|
||||
source.source.uri,
|
||||
target.source.uri
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Graph } from 'graphlib';
|
||||
import { URI, ID, Note, NoteLink } from './types';
|
||||
import { URI } from './common/uri';
|
||||
import { ID, Note, NoteLink } from './types';
|
||||
import { computeRelativeURI, nameToSlug, isSome } from './utils';
|
||||
import { Event, Emitter } from './common/event';
|
||||
|
||||
@@ -54,7 +55,7 @@ export class NoteGraph implements NoteGraphAPI {
|
||||
this.onDidAddNote = this.onDidAddNoteEmitter.event;
|
||||
this.onDidUpdateNote = this.onDidUpdateNoteEmitter.event;
|
||||
this.onDidDeleteNote = this.onDidDeleteEmitter.event;
|
||||
this.createIdFromURI = uri => uri;
|
||||
this.createIdFromURI = uri => uri.path;
|
||||
}
|
||||
|
||||
public setNote(note: Note): GraphNote {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Note } from '../types';
|
||||
import unified from 'unified';
|
||||
import { FoamConfig } from '../config';
|
||||
import { Logger } from '../utils/log';
|
||||
import { URI } from '../common/uri';
|
||||
|
||||
export interface FoamPlugin {
|
||||
name: string;
|
||||
@@ -38,15 +39,16 @@ export async function loadPlugins(config: FoamConfig): Promise<FoamPlugin[]> {
|
||||
if (!isFeatureEnabled) {
|
||||
return [];
|
||||
}
|
||||
const pluginDirs: string[] =
|
||||
pluginConfig.pluginFolders ?? findPluginDirs(config.workspaceFolders);
|
||||
const pluginDirs: URI[] =
|
||||
pluginConfig.pluginFolders?.map(URI.file) ??
|
||||
findPluginDirs(config.workspaceFolders);
|
||||
|
||||
const plugins = await Promise.all(
|
||||
pluginDirs
|
||||
.filter(dir => fs.statSync(dir).isDirectory)
|
||||
.filter(dir => fs.statSync(dir.fsPath).isDirectory)
|
||||
.map(async dir => {
|
||||
try {
|
||||
const pluginFile = path.join(dir, 'index.js');
|
||||
const pluginFile = path.join(dir.fsPath, 'index.js');
|
||||
fs.accessSync(pluginFile);
|
||||
Logger.info(`Found plugin at [${pluginFile}]. Loading..`);
|
||||
const plugin = validate(await import(pluginFile));
|
||||
@@ -60,19 +62,22 @@ export async function loadPlugins(config: FoamConfig): Promise<FoamPlugin[]> {
|
||||
return plugins.filter(isNotNull);
|
||||
}
|
||||
|
||||
function findPluginDirs(workspaceFolders: string[]) {
|
||||
function findPluginDirs(workspaceFolders: URI[]) {
|
||||
return workspaceFolders
|
||||
.map(root => path.join(root, '.foam', 'plugins'))
|
||||
.map(root => URI.joinPath(root, '.foam', 'plugins'))
|
||||
.reduce((acc, pluginDir) => {
|
||||
try {
|
||||
const content = fs
|
||||
.readdirSync(pluginDir)
|
||||
.map(dir => path.join(pluginDir, dir));
|
||||
return [...acc, ...content.filter(c => fs.statSync(c).isDirectory())];
|
||||
.readdirSync(pluginDir.fsPath)
|
||||
.map(dir => URI.joinPath(pluginDir, dir));
|
||||
return [
|
||||
...acc,
|
||||
...content.filter(c => fs.statSync(c.fsPath).isDirectory()),
|
||||
];
|
||||
} catch {
|
||||
return acc;
|
||||
}
|
||||
}, [] as string[]);
|
||||
}, [] as URI[]);
|
||||
}
|
||||
|
||||
function validate(plugin: any): FoamPlugin {
|
||||
|
||||
@@ -3,12 +3,31 @@ import { promisify } from 'util';
|
||||
import micromatch from 'micromatch';
|
||||
import fs from 'fs';
|
||||
import { Event, Emitter } from '../common/event';
|
||||
import { URI } from '../types';
|
||||
import { URI } from '../common/uri';
|
||||
import { FoamConfig } from '../config';
|
||||
import { Logger } from '../utils/log';
|
||||
import { isSome } from '../utils';
|
||||
import { IDisposable } from '../common/lifecycle';
|
||||
|
||||
const findAllFiles = promisify(glob);
|
||||
|
||||
export interface IWatcher {
|
||||
/**
|
||||
* An event which fires on file creation.
|
||||
*/
|
||||
onDidCreate: Event<URI>;
|
||||
|
||||
/**
|
||||
* An event which fires on file change.
|
||||
*/
|
||||
onDidChange: Event<URI>;
|
||||
|
||||
/**
|
||||
* An event which fires on file deletion.
|
||||
*/
|
||||
onDidDelete: Event<URI>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a source of files and content
|
||||
*/
|
||||
@@ -29,12 +48,6 @@ export interface IDataStore {
|
||||
*/
|
||||
isMatch: (uri: URI) => boolean;
|
||||
|
||||
/**
|
||||
* Filters a list of URIs based on whether they are a match
|
||||
* in this data store
|
||||
*/
|
||||
match: (uris: URI[]) => string[];
|
||||
|
||||
/**
|
||||
* An event which fires on file creation.
|
||||
*/
|
||||
@@ -54,26 +67,28 @@ export interface IDataStore {
|
||||
/**
|
||||
* File system based data store
|
||||
*/
|
||||
export class FileDataStore implements IDataStore {
|
||||
export class FileDataStore implements IDataStore, IDisposable {
|
||||
readonly onDidChangeEmitter = new Emitter<URI>();
|
||||
readonly onDidCreateEmitter = new Emitter<URI>();
|
||||
readonly onDidDeleteEmitter = new Emitter<URI>();
|
||||
readonly onDidCreate: Event<URI> = this.onDidCreateEmitter.event;
|
||||
readonly onDidChange: Event<URI> = this.onDidChangeEmitter.event;
|
||||
readonly onDidDelete: Event<URI> = this.onDidDeleteEmitter.event;
|
||||
readonly isMatch: (uri: URI) => boolean;
|
||||
readonly match: (uris: URI[]) => string[];
|
||||
|
||||
private _folders: readonly string[];
|
||||
private _includeGlobs: string[] = [];
|
||||
private _ignoreGlobs: string[] = [];
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(config: FoamConfig) {
|
||||
this._folders = config.workspaceFolders;
|
||||
constructor(config: FoamConfig, watcher?: IWatcher) {
|
||||
this._folders = config.workspaceFolders.map(f =>
|
||||
f.fsPath.replace(/\\/g, '/')
|
||||
);
|
||||
Logger.info('Workspace folders: ', this._folders);
|
||||
|
||||
let includeGlobs: string[] = [];
|
||||
let ignoreGlobs: string[] = [];
|
||||
config.workspaceFolders.forEach(folder => {
|
||||
this._folders.forEach(folder => {
|
||||
const withFolder = folderPlusGlob(folder);
|
||||
includeGlobs.push(
|
||||
this._includeGlobs.push(
|
||||
...config.includeGlobs.map(glob => {
|
||||
if (glob.endsWith('*')) {
|
||||
glob = `${glob}\\.(md|mdx|markdown)`;
|
||||
@@ -81,27 +96,59 @@ export class FileDataStore implements IDataStore {
|
||||
return withFolder(glob);
|
||||
})
|
||||
);
|
||||
ignoreGlobs.push(...config.ignoreGlobs.map(withFolder));
|
||||
this._ignoreGlobs.push(...config.ignoreGlobs.map(withFolder));
|
||||
});
|
||||
Logger.info('Glob patterns', {
|
||||
includeGlobs: this._includeGlobs,
|
||||
ignoreGlobs: this._ignoreGlobs,
|
||||
});
|
||||
|
||||
Logger.debug('Glob patterns', {
|
||||
includeGlobs,
|
||||
ignoreGlobs,
|
||||
});
|
||||
this.match = (files: URI[]) => {
|
||||
return micromatch(files, includeGlobs, {
|
||||
ignore: ignoreGlobs,
|
||||
if (isSome(watcher)) {
|
||||
this._disposables.push(
|
||||
watcher.onDidCreate(async uri => {
|
||||
if (this.isMatch(uri)) {
|
||||
Logger.info(`Created: ${uri.path}`);
|
||||
this.onDidCreateEmitter.fire(uri);
|
||||
}
|
||||
}),
|
||||
watcher.onDidChange(uri => {
|
||||
if (this.isMatch(uri)) {
|
||||
Logger.info(`Updated: ${uri.path}`);
|
||||
this.onDidChangeEmitter.fire(uri);
|
||||
}
|
||||
}),
|
||||
watcher.onDidDelete(uri => {
|
||||
if (this.isMatch(uri)) {
|
||||
Logger.info(`Deleted: ${uri.path}`);
|
||||
this.onDidDeleteEmitter.fire(uri);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
match(files: URI[]) {
|
||||
const matches = micromatch(
|
||||
files.map(f => f.fsPath),
|
||||
this._includeGlobs,
|
||||
{
|
||||
ignore: this._ignoreGlobs,
|
||||
nocase: true,
|
||||
});
|
||||
};
|
||||
this.isMatch = uri => this.match([uri]).length > 0;
|
||||
}
|
||||
);
|
||||
return matches.map(URI.file);
|
||||
}
|
||||
|
||||
isMatch(uri: URI) {
|
||||
return this.match([uri]).length > 0;
|
||||
}
|
||||
|
||||
async listFiles() {
|
||||
const files = (
|
||||
await Promise.all(
|
||||
this._folders.map(folder => {
|
||||
return findAllFiles(folderPlusGlob(folder)('**/*'));
|
||||
this._folders.map(async folder => {
|
||||
const res = await findAllFiles(folderPlusGlob(folder)('**/*'));
|
||||
return res.map(URI.file);
|
||||
})
|
||||
)
|
||||
).flat();
|
||||
@@ -109,7 +156,11 @@ export class FileDataStore implements IDataStore {
|
||||
}
|
||||
|
||||
async read(uri: URI) {
|
||||
return (await fs.promises.readFile(uri)).toString();
|
||||
return (await fs.promises.readFile(uri.fsPath)).toString();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposables.forEach(d => d.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// this file can't simply be .d.ts because the TS compiler wouldn't copy it to the dist directory
|
||||
// see https://stackoverflow.com/questions/56018167/typescript-does-not-copy-d-ts-files-to-build
|
||||
import { Position, Point } from 'unist';
|
||||
import { URI } from './common/uri';
|
||||
export { Position, Point };
|
||||
|
||||
export type URI = string;
|
||||
export type ID = string;
|
||||
|
||||
export interface NoteSource {
|
||||
@@ -42,5 +42,5 @@ export interface Note {
|
||||
}
|
||||
|
||||
export interface NoteParser {
|
||||
parse: (uri: string, text: string) => Note;
|
||||
parse: (uri: URI, text: string) => Note;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import path from 'path';
|
||||
import { posix } from 'path';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
import { URI, ID } from '../types';
|
||||
import { ID } from '../types';
|
||||
import { hash } from './core';
|
||||
import { URI } from '../common/uri';
|
||||
|
||||
export const uriToSlug = (noteUri: URI): string => {
|
||||
return GithubSlugger.slug(path.parse(noteUri).name);
|
||||
return GithubSlugger.slug(posix.parse(noteUri.path).name);
|
||||
};
|
||||
|
||||
export const nameToSlug = (noteName: string): string => {
|
||||
@@ -12,17 +13,26 @@ export const nameToSlug = (noteName: string): string => {
|
||||
};
|
||||
|
||||
export const hashURI = (uri: URI): ID => {
|
||||
return hash(path.normalize(uri));
|
||||
return hash(posix.normalize(uri.path));
|
||||
};
|
||||
|
||||
export const computeRelativePath = (source: URI, target: URI): string => {
|
||||
const relativePath = posix.relative(posix.dirname(source.path), target.path);
|
||||
return relativePath;
|
||||
};
|
||||
|
||||
export const getBasename = (uri: URI) => posix.parse(uri.path).name;
|
||||
|
||||
export const computeRelativeURI = (
|
||||
reference: URI,
|
||||
relativeSlug: string
|
||||
): URI => {
|
||||
// if no extension is provided, use the same extension as the source file
|
||||
const slug =
|
||||
path.extname(relativeSlug) !== ''
|
||||
posix.extname(relativeSlug) !== ''
|
||||
? relativeSlug
|
||||
: `${relativeSlug}${path.extname(reference)}`;
|
||||
return path.normalize(path.join(path.dirname(reference), slug));
|
||||
: `${relativeSlug}${posix.extname(reference.path)}`;
|
||||
return reference.with({
|
||||
path: posix.join(posix.dirname(reference.path), slug),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import * as path from 'path';
|
||||
import { createConfigFromFolders } from '../src/config';
|
||||
import { Logger } from '../src/utils/log';
|
||||
import { URI } from '../src/common/uri';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
const testFolder = URI.joinPath(URI.file(__dirname), 'test-config');
|
||||
|
||||
const testFolder = path.join(__dirname, 'test-config');
|
||||
describe('Foam configuration', () => {
|
||||
it('can read settings from config.json', () => {
|
||||
const config = createConfigFromFolders([path.join(testFolder, 'folder1')]);
|
||||
const config = createConfigFromFolders([
|
||||
URI.joinPath(testFolder, 'folder1'),
|
||||
]);
|
||||
expect(config.get('feature1.setting1.value')).toBeTruthy();
|
||||
expect(config.get('feature2.value')).toEqual(12);
|
||||
|
||||
@@ -14,8 +20,8 @@ describe('Foam configuration', () => {
|
||||
|
||||
it('can merge settings from multiple foam folders', () => {
|
||||
const config = createConfigFromFolders([
|
||||
path.join(testFolder, 'folder1'),
|
||||
path.join(testFolder, 'folder2'),
|
||||
URI.joinPath(testFolder, 'folder1'),
|
||||
URI.joinPath(testFolder, 'folder2'),
|
||||
]);
|
||||
|
||||
// override value
|
||||
@@ -31,7 +37,7 @@ describe('Foam configuration', () => {
|
||||
|
||||
it('cannot activate local plugins from workspace config', () => {
|
||||
const config = createConfigFromFolders([
|
||||
path.join(testFolder, 'enable-plugins'),
|
||||
URI.joinPath(testFolder, 'enable-plugins'),
|
||||
]);
|
||||
expect(config.get('experimental.localPlugins.enabled')).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { NoteGraph, createGraph } from '../src/note-graph';
|
||||
import { NoteLinkDefinition, Note } from '../src/types';
|
||||
import { uriToSlug } from '../src/utils';
|
||||
import { URI } from '../src/common/uri';
|
||||
import { Logger } from '../src/utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
const position = {
|
||||
start: { line: 1, column: 1 },
|
||||
@@ -21,7 +25,7 @@ export const createTestNote = (params: {
|
||||
return {
|
||||
properties: {},
|
||||
title: params.title ?? null,
|
||||
slug: uriToSlug(params.uri),
|
||||
slug: uriToSlug(URI.file(params.uri)),
|
||||
definitions: params.definitions ?? [],
|
||||
tags: new Set(),
|
||||
links: params.links
|
||||
@@ -36,7 +40,7 @@ export const createTestNote = (params: {
|
||||
eol: eol,
|
||||
end: documentEnd,
|
||||
contentStart: documentStart,
|
||||
uri: params.uri,
|
||||
uri: URI.file(params.uri),
|
||||
text: params.text ?? '',
|
||||
},
|
||||
};
|
||||
|
||||
72
packages/foam-core/test/datastore.test.ts
Normal file
72
packages/foam-core/test/datastore.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { createConfigFromObject } from '../src/config';
|
||||
import { Logger } from '../src/utils/log';
|
||||
import { URI } from '../src/common/uri';
|
||||
import { FileDataStore } from '../src';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
const testFolder = URI.joinPath(URI.file(__dirname), 'test-datastore');
|
||||
|
||||
function makeConfig(params: { include: string[]; ignore: string[] }) {
|
||||
return createConfigFromObject(
|
||||
[testFolder],
|
||||
params.include,
|
||||
params.ignore,
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
describe('Datastore', () => {
|
||||
it('defaults to including nothing and exclude nothing', async () => {
|
||||
const ds = new FileDataStore(
|
||||
makeConfig({
|
||||
include: [],
|
||||
ignore: [],
|
||||
})
|
||||
);
|
||||
expect(await ds.listFiles()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('returns only markdown files', async () => {
|
||||
const ds = new FileDataStore(
|
||||
makeConfig({
|
||||
include: ['**/*'],
|
||||
ignore: [],
|
||||
})
|
||||
);
|
||||
const res = toStringSet(await ds.listFiles());
|
||||
expect(res).toEqual(
|
||||
makeAbsolute([
|
||||
'/file-a.md',
|
||||
'/info/file-b.md',
|
||||
'/docs/file-in-nm.md',
|
||||
'/info/docs/file-in-sub-nm.md',
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('supports excludes', async () => {
|
||||
const ds = new FileDataStore(
|
||||
makeConfig({
|
||||
include: ['**/*'],
|
||||
ignore: ['**/docs/**'],
|
||||
})
|
||||
);
|
||||
const res = toStringSet(await ds.listFiles());
|
||||
expect(res).toEqual(makeAbsolute(['/file-a.md', '/info/file-b.md']));
|
||||
});
|
||||
});
|
||||
|
||||
function toStringSet(uris: URI[]) {
|
||||
return new Set(uris.map(uri => uri.path.toLocaleLowerCase()));
|
||||
}
|
||||
|
||||
function makeAbsolute(files: string[]) {
|
||||
return new Set(
|
||||
files.map(f =>
|
||||
URI.joinPath(testFolder, f)
|
||||
.path.toLocaleLowerCase()
|
||||
.replace(/\\/g, '/')
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import { applyTextEdit } from '../../src/janitor/apply-text-edit';
|
||||
import { Logger } from '../../src/utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
describe('applyTextEdit', () => {
|
||||
it('should return text with applied TextEdit in the end of the string', () => {
|
||||
|
||||
@@ -4,13 +4,17 @@ import { generateHeading } from '../../src/janitor';
|
||||
import { bootstrap } from '../../src/bootstrap';
|
||||
import { createConfigFromFolders } from '../../src/config';
|
||||
import { Services } from '../../src';
|
||||
import { URI } from '../../src/common/uri';
|
||||
import { FileDataStore } from '../../src/services/datastore';
|
||||
import { Logger } from '../../src/utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
describe('generateHeadings', () => {
|
||||
let _graph: NoteGraphAPI;
|
||||
beforeAll(async () => {
|
||||
const config = createConfigFromFolders([
|
||||
path.join(__dirname, '../__scaffold__'),
|
||||
URI.file(path.join(__dirname, '..', '__scaffold__')),
|
||||
]);
|
||||
const services: Services = {
|
||||
dataStore: new FileDataStore(config),
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import * as path from 'path';
|
||||
import { NoteGraphAPI } from '../../src/note-graph';
|
||||
import { NoteGraphAPI, GraphNote } from '../../src/note-graph';
|
||||
import { generateLinkReferences } from '../../src/janitor';
|
||||
import { bootstrap } from '../../src/bootstrap';
|
||||
import { createConfigFromFolders } from '../../src/config';
|
||||
import { Services } from '../../src';
|
||||
import { FileDataStore } from '../../src/services/datastore';
|
||||
import { Logger } from '../../src/utils/log';
|
||||
import { URI } from '../../src/common/uri';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
describe('generateLinkReferences', () => {
|
||||
let _graph: NoteGraphAPI;
|
||||
|
||||
beforeAll(async () => {
|
||||
const config = createConfigFromFolders([
|
||||
path.join(__dirname, '../__scaffold__'),
|
||||
URI.file(path.join(__dirname, '..', '__scaffold__')),
|
||||
]);
|
||||
const services: Services = {
|
||||
dataStore: new FileDataStore(config),
|
||||
@@ -26,23 +30,26 @@ describe('generateLinkReferences', () => {
|
||||
it('should add link references to a file that does not have them', () => {
|
||||
const note = _graph.getNotes({ slug: 'index' })[0];
|
||||
const expected = {
|
||||
newText: `
|
||||
newText: textForNote(
|
||||
note,
|
||||
`
|
||||
[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[first-document]: first-document "First Document"
|
||||
[second-document]: second-document "Second Document"
|
||||
[file-without-title]: file-without-title "file-without-title"
|
||||
[//end]: # "Autogenerated link references"`,
|
||||
[//end]: # "Autogenerated link references"`
|
||||
),
|
||||
range: {
|
||||
start: {
|
||||
start: pointForNote(note, {
|
||||
line: 10,
|
||||
column: 1,
|
||||
offset: 140,
|
||||
},
|
||||
end: {
|
||||
}),
|
||||
end: pointForNote(note, {
|
||||
line: 10,
|
||||
column: 1,
|
||||
offset: 140,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -59,16 +66,16 @@ describe('generateLinkReferences', () => {
|
||||
const expected = {
|
||||
newText: '',
|
||||
range: {
|
||||
start: {
|
||||
start: pointForNote(note, {
|
||||
line: 7,
|
||||
column: 1,
|
||||
offset: 105,
|
||||
},
|
||||
end: {
|
||||
}),
|
||||
end: pointForNote(note, {
|
||||
line: 9,
|
||||
column: 43,
|
||||
offset: 269,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -83,20 +90,23 @@ describe('generateLinkReferences', () => {
|
||||
const note = _graph.getNotes({ slug: 'first-document' })[0];
|
||||
|
||||
const expected = {
|
||||
newText: `[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
newText: textForNote(
|
||||
note,
|
||||
`[//begin]: # "Autogenerated link references for markdown compatibility"
|
||||
[file-without-title]: file-without-title "file-without-title"
|
||||
[//end]: # "Autogenerated link references"`,
|
||||
[//end]: # "Autogenerated link references"`
|
||||
),
|
||||
range: {
|
||||
start: {
|
||||
start: pointForNote(note, {
|
||||
line: 9,
|
||||
column: 1,
|
||||
offset: 145,
|
||||
},
|
||||
end: {
|
||||
}),
|
||||
end: pointForNote(note, {
|
||||
line: 11,
|
||||
column: 43,
|
||||
offset: 312,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -117,3 +127,34 @@ describe('generateLinkReferences', () => {
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Will adjust a text line separator to match
|
||||
* what is used by the note
|
||||
* Necessary when running tests on windows
|
||||
*
|
||||
* @param note the note we are adjusting for
|
||||
* @param text starting text, using a \n line separator
|
||||
*/
|
||||
function textForNote(note: GraphNote, text: string): string {
|
||||
return text.split('\n').join(note.source.eol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will adjust a point to take into account the EOL length
|
||||
* of the note
|
||||
* Necessary when running tests on windows
|
||||
*
|
||||
* @param note the note we are adjusting for
|
||||
* @param pos starting position
|
||||
*/
|
||||
function pointForNote(
|
||||
note: GraphNote,
|
||||
pos: { line: number; column: number; offset: number }
|
||||
) {
|
||||
const rows = pos.line - 1;
|
||||
return {
|
||||
...pos,
|
||||
offset: pos.offset - rows + rows * note.source.eol.length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import {
|
||||
} from '../src/markdown-provider';
|
||||
import { NoteGraph } from '../src/note-graph';
|
||||
import { ParserPlugin } from '../src/plugins';
|
||||
import { URI } from '../src/common/uri';
|
||||
import { Logger } from '../src/utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
const pageA = `
|
||||
# Page A
|
||||
@@ -32,7 +36,8 @@ const pageE = `
|
||||
# Page E
|
||||
`;
|
||||
|
||||
const createNoteFromMarkdown = createMarkdownParser([]).parse;
|
||||
const createNoteFromMarkdown = (path: string, content: string) =>
|
||||
createMarkdownParser([]).parse(URI.file(path), content);
|
||||
|
||||
describe('Markdown loader', () => {
|
||||
it('Converts markdown to notes', () => {
|
||||
@@ -295,7 +300,7 @@ describe('parser plugins', () => {
|
||||
|
||||
it('can augment the parsing of the file', async () => {
|
||||
const note1 = parser.parse(
|
||||
'/path/to/a',
|
||||
URI.file('/path/to/a'),
|
||||
`
|
||||
This is a test note without headings.
|
||||
But with some content.
|
||||
@@ -304,7 +309,7 @@ But with some content.
|
||||
expect(note1.properties.hasHeading).toBeUndefined();
|
||||
|
||||
const note2 = parser.parse(
|
||||
'/path/to/a',
|
||||
URI.file('/path/to/a'),
|
||||
`
|
||||
# This is a note with header
|
||||
and some content`
|
||||
|
||||
@@ -4,6 +4,10 @@ import { createMarkdownParser } from '../src/markdown-provider';
|
||||
import { createGraph } from '../src/note-graph';
|
||||
import { createTestNote } from './core.test';
|
||||
import { FoamConfig, createConfigFromObject } from '../src/config';
|
||||
import { URI } from '../src/common/uri';
|
||||
import { Logger } from '../src/utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
const config: FoamConfig = createConfigFromObject([], [], [], {
|
||||
experimental: {
|
||||
@@ -60,7 +64,7 @@ describe('Foam plugins', () => {
|
||||
const parser = createMarkdownParser([parserPlugin!]);
|
||||
|
||||
const note = parser.parse(
|
||||
'/path/to/a',
|
||||
URI.file('/path/to/a'),
|
||||
`
|
||||
# This is a note with header
|
||||
and some content`
|
||||
|
||||
0
packages/foam-core/test/test-datastore/file-a.md
Normal file
0
packages/foam-core/test/test-datastore/file-a.md
Normal file
@@ -5,14 +5,22 @@ import {
|
||||
computeRelativeURI,
|
||||
extractHashtags,
|
||||
} from '../src/utils';
|
||||
import { URI } from '../src/common/uri';
|
||||
import { Logger } from '../src/utils/log';
|
||||
|
||||
Logger.setLevel('error');
|
||||
|
||||
describe('URI utils', () => {
|
||||
it('supports various cases', () => {
|
||||
expect(uriToSlug('/this/is/a/path.md')).toEqual('path');
|
||||
expect(uriToSlug('../a/relative/path.md')).toEqual('path');
|
||||
expect(uriToSlug('another/relative/path.md')).toEqual('path');
|
||||
expect(uriToSlug('no-directory.markdown')).toEqual('no-directory');
|
||||
expect(uriToSlug('many.dots.name.markdown')).toEqual('manydotsname');
|
||||
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('converts a name to a slug', () => {
|
||||
@@ -23,22 +31,24 @@ describe('URI utils', () => {
|
||||
});
|
||||
|
||||
it('normalizes URI before hashing', () => {
|
||||
expect(hashURI('/this/is/a/path.md')).toEqual(
|
||||
hashURI('/this/has/../is/a/path.md')
|
||||
expect(hashURI(URI.file('/this/is/a/path.md'))).toEqual(
|
||||
hashURI(URI.file('/this/has/../is/a/path.md'))
|
||||
);
|
||||
expect(hashURI('this/is/a/path.md')).toEqual(
|
||||
hashURI('this/has/../is/a/path.md')
|
||||
expect(hashURI(URI.file('this/is/a/path.md'))).toEqual(
|
||||
hashURI(URI.file('this/has/../is/a/path.md'))
|
||||
);
|
||||
});
|
||||
|
||||
it('computes a relative uri using a slug', () => {
|
||||
expect(computeRelativeURI('/my/file.md', '../hello.md')).toEqual(
|
||||
'/hello.md'
|
||||
expect(computeRelativeURI(URI.file('/my/file.md'), '../hello.md')).toEqual(
|
||||
URI.file('/hello.md')
|
||||
);
|
||||
expect(computeRelativeURI('/my/file.md', '../hello')).toEqual('/hello.md');
|
||||
expect(computeRelativeURI('/my/file.markdown', '../hello')).toEqual(
|
||||
'/hello.markdown'
|
||||
expect(computeRelativeURI(URI.file('/my/file.md'), '../hello')).toEqual(
|
||||
URI.file('/hello.md')
|
||||
);
|
||||
expect(
|
||||
computeRelativeURI(URI.file('/my/file.markdown'), '../hello')
|
||||
).toEqual(URI.file('/hello.markdown'));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
"build": "tsc -p ./",
|
||||
"test": "jest",
|
||||
"lint": "eslint src --ext ts",
|
||||
"clean": "tsc --build ./tsconfig.json ../foam-core/tsconfig.json --clean",
|
||||
"clean": "rimraf out",
|
||||
"watch": "tsc --build ./tsconfig.json ../foam-core/tsconfig.json --watch",
|
||||
"vscode:start-debugging": "yarn clean && yarn watch",
|
||||
"vscode:prepublish": "yarn npm-install && yarn run build",
|
||||
|
||||
@@ -24,7 +24,7 @@ async function openDailyNoteFor(date?: Date) {
|
||||
await focusNote(dailyNotePath, isNew);
|
||||
}
|
||||
function getDailyNotePath(configuration: WorkspaceConfiguration, date: Date) {
|
||||
const rootDirectory = workspace.workspaceFolders[0].uri.fsPath;
|
||||
const rootDirectory = workspace.workspaceFolders[0].uri.path;
|
||||
const dailyNoteDirectory: string =
|
||||
configuration.get("openDailyNote.directory") ?? ".";
|
||||
const dailyNoteFilename = getDailyNoteFileName(configuration, date);
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
"use strict";
|
||||
|
||||
import { workspace, ExtensionContext, window } from "vscode";
|
||||
|
||||
import { workspace, ExtensionContext, window, Uri } from "vscode";
|
||||
import {
|
||||
bootstrap,
|
||||
FoamConfig,
|
||||
Foam,
|
||||
FileDataStore,
|
||||
Services,
|
||||
isDisposable,
|
||||
Logger
|
||||
Logger,
|
||||
FileDataStore
|
||||
} from "foam-core";
|
||||
|
||||
import { features } from "./features";
|
||||
import { getConfigFromVscode } from "./services/config";
|
||||
import { VsCodeOutputLogger, exposeLogger } from "./services/logging";
|
||||
|
||||
let foam: Foam | null = null;
|
||||
import { VsCodeDataStore } from "./services/datastore";
|
||||
|
||||
export async function activate(context: ExtensionContext) {
|
||||
const logger = new VsCodeOutputLogger();
|
||||
@@ -27,24 +24,8 @@ export async function activate(context: ExtensionContext) {
|
||||
Logger.info("Starting Foam");
|
||||
|
||||
const config: FoamConfig = getConfigFromVscode();
|
||||
const dataStore = new FileDataStore(config);
|
||||
|
||||
const watcher = workspace.createFileSystemWatcher("**/*");
|
||||
watcher.onDidCreate(uri => {
|
||||
if (dataStore.isMatch(uri.fsPath)) {
|
||||
dataStore.onDidCreateEmitter.fire(uri.fsPath);
|
||||
}
|
||||
});
|
||||
watcher.onDidChange(uri => {
|
||||
if (dataStore.isMatch(uri.fsPath)) {
|
||||
dataStore.onDidChangeEmitter.fire(uri.fsPath);
|
||||
}
|
||||
});
|
||||
watcher.onDidDelete(uri => {
|
||||
if (dataStore.isMatch(uri.fsPath)) {
|
||||
dataStore.onDidDeleteEmitter.fire(uri.fsPath);
|
||||
}
|
||||
});
|
||||
const dataStore = new FileDataStore(config, watcher);
|
||||
|
||||
const services: Services = {
|
||||
dataStore: dataStore
|
||||
@@ -55,8 +36,10 @@ export async function activate(context: ExtensionContext) {
|
||||
f.activate(context, foamPromise);
|
||||
});
|
||||
|
||||
foam = await foamPromise;
|
||||
const foam = await foamPromise;
|
||||
Logger.info(`Loaded ${foam.notes.getNotes().length} notes`);
|
||||
|
||||
context.subscriptions.push(dataStore, foam, watcher);
|
||||
} catch (e) {
|
||||
Logger.error("An error occurred while bootstrapping Foam", e);
|
||||
window.showErrorMessage(
|
||||
@@ -64,9 +47,3 @@ export async function activate(context: ExtensionContext) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
if (isDisposable(foam)) {
|
||||
foam?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ import { FoamFeature } from "../types";
|
||||
import { TextEncoder } from "util";
|
||||
import { focusNote } from "../utils";
|
||||
|
||||
const templatesDir = `${workspace.workspaceFolders[0].uri.fsPath}/.foam/templates`;
|
||||
const templatesDir = `${workspace.workspaceFolders[0].uri.path}/.foam/templates`;
|
||||
|
||||
async function getTemplates(): Promise<string[]> {
|
||||
const templates = await workspace.findFiles(".foam/templates/**.md");
|
||||
// parse title, not whole file!
|
||||
return templates.map(template => path.basename(template.fsPath));
|
||||
return templates.map(template => path.basename(template.path));
|
||||
}
|
||||
|
||||
const feature: FoamFeature = {
|
||||
@@ -30,7 +30,7 @@ const feature: FoamFeature = {
|
||||
const currentDir =
|
||||
activeFile !== undefined
|
||||
? path.dirname(activeFile)
|
||||
: workspace.workspaceFolders[0].uri.fsPath;
|
||||
: workspace.workspaceFolders[0].uri.path;
|
||||
const selectedTemplate = await window.showQuickPick(templates);
|
||||
const folder = await window.showInputBox({
|
||||
prompt: `Where should the template be created?`,
|
||||
|
||||
@@ -27,7 +27,7 @@ const feature: FoamFeature = {
|
||||
|
||||
vscode.window.onDidChangeActiveTextEditor(e => {
|
||||
if (e.document.uri.scheme === "file") {
|
||||
const note = foam.notes.getNoteByURI(e.document.uri.fsPath);
|
||||
const note = foam.notes.getNoteByURI(e.document.uri);
|
||||
if (isSome(note)) {
|
||||
panel.webview.postMessage({
|
||||
type: "didSelectNote",
|
||||
@@ -113,8 +113,7 @@ async function createGraphPanel(foam: Foam, context: vscode.ExtensionContext) {
|
||||
|
||||
case "webviewDidSelectNode":
|
||||
const noteId = message.payload;
|
||||
const noteUri = foam.notes.getNote(noteId).source.uri;
|
||||
const openPath = vscode.Uri.file(noteUri);
|
||||
const openPath = foam.notes.getNote(noteId).source.uri;
|
||||
|
||||
vscode.workspace.openTextDocument(openPath).then(doc => {
|
||||
vscode.window.showTextDocument(doc, vscode.ViewColumn.One);
|
||||
|
||||
@@ -81,15 +81,15 @@ async function runJanitor(foam: Foam) {
|
||||
);
|
||||
|
||||
const dirtyEditorsFileName = dirtyTextDocuments.map(
|
||||
dirtyTextDocument => dirtyTextDocument.fileName
|
||||
dirtyTextDocument => dirtyTextDocument.uri.path
|
||||
);
|
||||
|
||||
const dirtyNotes = notes.filter(note =>
|
||||
dirtyEditorsFileName.includes(note.source.uri)
|
||||
dirtyEditorsFileName.includes(note.source.uri.path)
|
||||
);
|
||||
|
||||
const nonDirtyNotes = notes.filter(
|
||||
note => !dirtyEditorsFileName.includes(note.source.uri)
|
||||
note => !dirtyEditorsFileName.includes(note.source.uri.path)
|
||||
);
|
||||
|
||||
const wikilinkSetting = getWikilinkDefinitionSetting();
|
||||
@@ -125,7 +125,7 @@ async function runJanitor(foam: Foam) {
|
||||
text = definitions ? applyTextEdit(text, definitions) : text;
|
||||
text = heading ? applyTextEdit(text, heading) : text;
|
||||
|
||||
return fs.promises.writeFile(note.source.uri, text);
|
||||
return fs.promises.writeFile(note.source.uri.path, text);
|
||||
});
|
||||
|
||||
await Promise.all(fileWritePromises);
|
||||
@@ -135,7 +135,7 @@ async function runJanitor(foam: Foam) {
|
||||
for (const doc of dirtyTextDocuments) {
|
||||
const editor = await window.showTextDocument(doc);
|
||||
const note = dirtyNotes.find(
|
||||
n => n.source.uri === editor.document.fileName
|
||||
n => n.source.uri.path === editor.document.uri.path
|
||||
)!;
|
||||
|
||||
// Get edits
|
||||
|
||||
@@ -123,9 +123,9 @@ export class TagReference extends vscode.TreeItem {
|
||||
constructor(tag: string, note: Note) {
|
||||
super(note.title, vscode.TreeItemCollapsibleState.None);
|
||||
this.title = note.title;
|
||||
this.description = note.source.uri;
|
||||
this.description = note.source.uri.path;
|
||||
this.tooltip = this.description;
|
||||
const resourceUri = vscode.Uri.file(note.source.uri);
|
||||
const resourceUri = note.source.uri;
|
||||
let selection: vscode.Range | null = null;
|
||||
// TODO move search fn to core
|
||||
const lines = note.source.text.split(/\r?\n/);
|
||||
|
||||
@@ -72,7 +72,7 @@ const feature: FoamFeature = {
|
||||
|
||||
function updateDocumentInNoteGraph(foam: Foam, document: TextDocument) {
|
||||
foam.notes.setNote(
|
||||
foam.parse(document.fileName, document.getText(), docConfig.eol)
|
||||
foam.parse(document.uri, document.getText(), docConfig.eol)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,15 +138,13 @@ function generateReferenceList(
|
||||
return [];
|
||||
}
|
||||
|
||||
const filePath = doc.fileName;
|
||||
|
||||
const note = foam.getNoteByURI(filePath);
|
||||
const note = foam.getNoteByURI(doc.uri);
|
||||
|
||||
// Should never happen as `doc` is usually given by `editor.document`, which
|
||||
// binds to an opened note.
|
||||
if (!note) {
|
||||
console.warn(
|
||||
`Can't find note for URI ${filePath} before attempting to generate its markdown reference list`
|
||||
`Can't find note for URI ${doc.uri.path} before attempting to generate its markdown reference list`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -6,12 +6,10 @@ import { getIgnoredFilesSetting } from "../settings";
|
||||
// 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.fsPath
|
||||
);
|
||||
const excludeGlobs: string[] = getIgnoredFilesSetting();
|
||||
const workspaceFolders = workspace.workspaceFolders.map(dir => dir.uri);
|
||||
const excludeGlobs = getIgnoredFilesSetting();
|
||||
|
||||
return createConfigFromFolders(workspaceFolders, {
|
||||
ignore: excludeGlobs
|
||||
ignore: excludeGlobs.map(g => g.toString())
|
||||
});
|
||||
};
|
||||
|
||||
68
packages/foam-vscode/src/services/datastore.ts
Normal file
68
packages/foam-vscode/src/services/datastore.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
IDataStore,
|
||||
Event,
|
||||
URI,
|
||||
FoamConfig,
|
||||
IDisposable,
|
||||
Logger
|
||||
} from "foam-core";
|
||||
import { workspace, FileSystemWatcher, EventEmitter } from "vscode";
|
||||
import { TextDecoder } from "util";
|
||||
import { isSome } from "../utils";
|
||||
|
||||
export class VsCodeDataStore implements IDataStore, IDisposable {
|
||||
onDidCreateEmitter = new EventEmitter<URI>();
|
||||
onDidChangeEmitter = new EventEmitter<URI>();
|
||||
onDidDeleteEmitter = new EventEmitter<URI>();
|
||||
onDidCreate: Event<URI> = this.onDidCreateEmitter.event;
|
||||
onDidChange: Event<URI> = this.onDidChangeEmitter.event;
|
||||
onDidDelete: Event<URI> = this.onDidDeleteEmitter.event;
|
||||
|
||||
watcher: FileSystemWatcher;
|
||||
files: URI[];
|
||||
|
||||
constructor(private config: FoamConfig) {
|
||||
this.watcher = workspace.createFileSystemWatcher("**/*");
|
||||
this.watcher.onDidCreate(async uri => {
|
||||
await this.listFiles();
|
||||
if (this.isMatch(uri)) {
|
||||
Logger.info("Created: ", uri);
|
||||
this.onDidCreateEmitter.fire(uri);
|
||||
}
|
||||
});
|
||||
this.watcher.onDidChange(uri => {
|
||||
if (this.isMatch(uri)) {
|
||||
Logger.info("Updated: ", uri);
|
||||
this.onDidChangeEmitter.fire(uri);
|
||||
}
|
||||
});
|
||||
this.watcher.onDidDelete(uri => {
|
||||
if (this.isMatch(uri)) {
|
||||
Logger.info("Deleted: ", uri);
|
||||
this.files = this.files.filter(f => f.path !== uri.path);
|
||||
this.onDidDeleteEmitter.fire(uri);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async listFiles(): Promise<URI[]> {
|
||||
this.files = await workspace.findFiles(
|
||||
`{${this.config.includeGlobs.join(",")}}`,
|
||||
`{${this.config.ignoreGlobs.join(",")}}`
|
||||
);
|
||||
|
||||
return this.files;
|
||||
}
|
||||
|
||||
isMatch(uri: URI): boolean {
|
||||
return isSome(this.files.find(f => f.path === uri.path));
|
||||
}
|
||||
|
||||
async read(uri: URI): Promise<string> {
|
||||
return new TextDecoder().decode(await workspace.fs.readFile(uri));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.watcher.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { workspace } from "vscode";
|
||||
import { workspace, GlobPattern } from "vscode";
|
||||
import { LogLevel } from "foam-core";
|
||||
|
||||
export enum LinkReferenceDefinitionsSetting {
|
||||
@@ -17,8 +17,11 @@ export function getWikilinkDefinitionSetting(): LinkReferenceDefinitionsSetting
|
||||
}
|
||||
|
||||
/** Retrieve the list of file ignoring globs. */
|
||||
export function getIgnoredFilesSetting(): string[] {
|
||||
return workspace.getConfiguration("foam.files").get("ignore");
|
||||
export function getIgnoredFilesSetting(): GlobPattern[] {
|
||||
return [
|
||||
...workspace.getConfiguration().get("foam.files.ignore", []),
|
||||
...Object.keys(workspace.getConfiguration().get("files.exclude", {}))
|
||||
];
|
||||
}
|
||||
|
||||
/** Retrieves the maximum length for a Graph node title. */
|
||||
|
||||
Reference in New Issue
Block a user