Compare commits

...

2 Commits

Author SHA1 Message Date
Jani Eväkallio
c2f46af479 Incredibly non-functional prototype of block references 2020-09-11 20:16:41 +01:00
Jani Eväkallio
b0051b4da3 Setup Language Server boilerplate (#258) 2020-09-10 10:49:02 +01:00
18 changed files with 893 additions and 9 deletions

19
.vscode/launch.json vendored
View File

@@ -71,6 +71,25 @@
"cwd": "${workspaceFolder}/packages/foam-core",
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "${defaultBuildTask}"
},
{
"name": "Debug Language Server",
"type": "node",
"request": "attach",
"port": 6009,
// large timeout to account for typescript watch startup time
"timeout": 30000,
"restart": true,
"trace": true,
"localRoot": "${workspaceFolder}/packages/foam-language-server/src/",
"remoteRoot": "${workspaceFolder}/packages/foam-language-server/dist/",
"outFiles": ["${workspaceFolder}/packages/foam-language-server/dist/**/*.js"]
},
],
"compounds": [
{
"name": "Run Extension & Debug LSP",
"configurations": ["Run VSCode Extension", "Debug Language Server"]
}
]
}

42
docs/lsp-architecture.md Normal file
View File

@@ -0,0 +1,42 @@
# LSP Architecture
## Information requirements
### Document Completion
- List of all files in the workspace
- Name
- Title
- Short preview of content
### Anchor completion
- List of all headings in the workspace
- List of all paragraphs in the workspace
- With full content
- With context of where they are in the document
- With hash ids
- With document positions
## Link highlight
- All links
- With positions
## Go to / peek link definition
- Get link at position
- Get link target document
- Get link target range
## Go to / peek references
- Get all links that point to file
- Get all links that point to section/id
- Get all instances of a hashtag
## Symbols
- Get all links in a document
- Get all hashtags in a document
- Get all block ids in a document

View File

@@ -0,0 +1,33 @@
{
"name": "foam-language-server",
"author": "Jani Eväkallio",
"repository": "https://github.com/foambubble/foam",
"version": "0.3.0",
"license": "MIT",
"files": [
"dist"
],
"scripts": {
"clean": "rimraf dist",
"build": "tsdx build --tsconfig ./tsconfig.build.json",
"test": "tsdx test",
"lint": "tsdx lint src test",
"watch": "tsdx watch",
"prepare": "npm run build"
},
"devDependencies": {
"tsdx": "^0.13.2",
"tslib": "^2.0.0",
"typescript": "^3.9.5"
},
"dependencies": {
"file-uri-to-path": "^2.0.0",
"foam-core": "^0.3.0",
"remark-stringify": "^8.1.1",
"short-hash": "^1.0.0",
"vscode-languageserver": "^6.1.1",
"vscode-languageserver-textdocument": "^1.0.1"
},
"main": "dist/index.js",
"types": "dist/index.d.ts"
}

View File

@@ -0,0 +1,100 @@
import unified from 'unified';
import markdownParse from 'remark-parse';
import markdownStringify from 'remark-stringify';
import wikiLinkPlugin from 'remark-wiki-link';
import frontmatterPlugin from 'remark-frontmatter';
import shortHash from 'short-hash';
import visit from 'unist-util-visit';
import { Node, Parent, Point, Position } from 'unist';
import GitHubSlugger from 'github-slugger';
let processor: unified.Processor | null = null;
let stringifier = unified()
.use(markdownStringify)
.use(wikiLinkPlugin);
function parse(markdown: string): Node {
processor =
processor ||
unified()
.use(markdownParse, { gfm: true })
.use(frontmatterPlugin, ['yaml'])
.use(wikiLinkPlugin);
return processor.parse(markdown);
}
export interface FileBlock {
text: string;
type: string;
hash?: string;
sort: string;
key: string;
position: Position;
}
export function getFileBlocks(markdown: string): FileBlock[] {
let tree = parse(markdown);
let blocks: FileBlock[] = [];
let order = 0;
visit(tree, node => {
const block = getFileBlockFromNode(node, ++order);
if (block) {
blocks.push(block);
}
});
return blocks;
}
let previouslySeenListItemLine = -1;
function getFileBlockFromNode(node: Node, order: number): FileBlock | null {
switch (node.type) {
case 'heading': {
const text = (node.children as any[])[0].value as string;
return {
text,
key: new GitHubSlugger().slug(text),
type: node.type,
sort: order.toString().padStart(6, '0'),
position: node.position!,
};
}
case 'listItem': {
// @todo unhack
previouslySeenListItemLine = node.position!.start.line;
return null;
}
case 'paragraph': {
let text;
let existingHash;
let hashRegex = /\^(\w+)\s{0,}$/;
try {
text = stringifier!.stringify(node);
let matches = hashRegex.exec(text);
if (matches) {
existingHash = matches[1];
}
} catch (e) {
console.log('Stringify failed', e.message, e, node);
return null;
}
const type = node.position!.start.line === previouslySeenListItemLine ? 'listItem' : 'paragraph';
return {
text,
hash: existingHash,
key: existingHash || shortHash(text),
type,
sort: order.toString().padStart(6, '0'),
position: node.position!,
};
}
default:
return null;
}
}

View File

@@ -0,0 +1,423 @@
import {
createConnection,
ProposedFeatures,
InitializeParams,
DidChangeConfigurationNotification,
CompletionItem,
CompletionItemKind,
TextDocuments,
TextDocumentPositionParams,
TextDocumentSyncKind,
InsertTextFormat,
InitializeResult,
Position,
TextDocumentContentChangeEvent,
Range,
} from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { getWorkspaceFiles } from './workspace';
import { getFileBlocks, FileBlock } from './file';
const innerUpdate = TextDocument.update;
TextDocument.update = (document: TextDocument, changes: TextDocumentContentChangeEvent[], version: number) => {
const updated = innerUpdate(document, changes, version);
siphonOnDidChangeTextDocumentEvent(document, changes);
return updated;
}
const textDocumentManager = new TextDocuments(TextDocument);
let workspaceFiles: {
title: string;
fileName: string;
filePath: string;
target: string;
preview: string;
}[] = [];
// Create a connection for the server, using Node's IPC as a transport.
// Also include all preview / proposed LSP features.
let connection = createConnection(ProposedFeatures.all);
let hasConfigurationCapability: boolean = false;
let hasWorkspaceFolderCapability: boolean = false;
connection.onInitialize((params: InitializeParams) => {
let capabilities = params.capabilities;
// Does the client support the `workspace/configuration` request?
// If not, we fall back using global settings.
hasConfigurationCapability = !!(
capabilities.workspace && !!capabilities.workspace.configuration
);
hasWorkspaceFolderCapability = !!(
capabilities.workspace && !!capabilities.workspace.workspaceFolders
);
const result: InitializeResult = {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
// Tell the client that this server supports code completion.
executeCommandProvider: {
commands: ['foam/insertHash'],
},
completionProvider: {
triggerCharacters: ['[', '#'],
resolveProvider: true,
},
},
};
if (hasWorkspaceFolderCapability) {
result.capabilities.workspace = {
workspaceFolders: {
supported: true,
},
};
}
return result;
});
connection.onInitialized(async () => {
if (hasConfigurationCapability) {
// Register for all configuration changes.
connection.client.register(
DidChangeConfigurationNotification.type,
undefined
);
}
if (hasWorkspaceFolderCapability) {
const folders = await connection.workspace.getWorkspaceFolders();
if (!folders || folders.length === 0) {
console.error('No workspace folders found');
} else {
if (folders.length > 1) {
console.log('Multi-root workspaces not yet supported');
}
let folder = folders[0];
console.log('Initializing workspace at ' + folder.uri);
workspaceFiles = await getWorkspaceFiles(folder.uri);
}
}
textDocumentManager.onDidOpen(e => {
console.log('onDidOpen', e.document.uri);
});
textDocumentManager.onDidClose(e => {
// console.log('onDidClose', e.document);
});
textDocumentManager.onDidChangeContent(e => {
console.log('onDidChange', e.document.uri);
});
textDocumentManager.onWillSave(e => {
// console.log('onWillSave', e.document, e.reason);
});
textDocumentManager.onWillSaveWaitUntil((e, cancellationToken) => {
// console.log('onWillSaveTextDocumentWaitUntil', e.document, e.reason);
// Could return text edits here;
return [];
});
textDocumentManager.onDidSave(e => {
// console.log('onDidSave', e.document);
});
});
connection.onDidChangeConfiguration(change => {
if (hasConfigurationCapability) {
// todo
}
});
connection.onDidChangeWatchedFiles(_change => {
// Monitored files have change in VSCode
connection.console.log('We received an file change event');
});
connection.onExecuteCommand(async e => {
if (e.command === 'foam/insertHash') {
const {
sourceUri,
sourcePosition,
targetUri,
targetBlock,
}: {
sourceUri: string;
sourcePosition: Position;
targetUri: string;
targetBlock: FileBlock;
} = e.arguments![0];
const astPosition = targetBlock.position;
const docPosition = {
line: astPosition.end.line - 1,
character: astPosition.end.column - 1,
};
const result = await connection.workspace.applyEdit({
label: 'Insert generated hash',
edit: {
changes: {
[targetUri]: [
{
newText: ` ^${targetBlock.key}`,
range: {
start: docPosition,
end: docPosition,
},
},
],
},
},
});
if (result.applied) {
activeHashEdit = {
sourceUri: sourceUri,
sourceRange: {
start: sourcePosition,
end: {
...sourcePosition,
character: sourcePosition.character + targetBlock.key.length,
},
},
targetUri: targetUri,
targetRange: {
start: { ...docPosition, character: docPosition.character },
end: {
...docPosition,
character: docPosition.character + targetBlock.key.length + 2, // +" ^"
},
},
};
}
console.log(
'foam/insertHash',
sourceUri,
sourcePosition,
targetUri,
targetBlock
);
} else {
console.log('unrecognized command', e.command, e.arguments);
}
});
function getAvailableSections(
uri: string,
position: Position
): CompletionItem[] {
const targetUri =
'file:///Users/swanson/dev/foam-link-completion-demo/Principles.md';
const document = textDocumentManager.get(targetUri);
const blocks = getFileBlocks(document!.getText());
// try to find the end brackets, so we can remove them and add them
// back on the other side of our cursor, so when the insertion is
// completed, the text cursor will remain on the outside of the brackets
// const sections = getMockSections();
// const document = textDocumentManager.get(uri);
// if (document) {
// const endBracketRange = {
// start: position,
// end: { ...position, character: position.character + 2 },
// };
// if (document.getText(endBracketRange) == ']]') {
// return sections.map((section) => ({
// label: `#${section}`,
// kind: CompletionItemKind.Text,
// insertTextFormat: InsertTextFormat.Snippet,
// textEdit: {
// newText: '${1:' + shortHash(section) + '}]] $0',
// range: endBracketRange
// },
// }));
// }
// }
const kindsByType: { [x: string]: CompletionItemKind } = {
heading: CompletionItemKind.Folder,
paragraph: CompletionItemKind.Text,
listItem: CompletionItemKind.Keyword,
};
// no luck locating end brocket range, just inject the value
// and let user handle in manually
return blocks.map(block => {
const labelPrefix = block.type === 'heading' ? '# ' : '';
const insertNewHash = block.type !== 'heading' && !block.hash;
return {
label: `${labelPrefix}${block.text}`,
detail: block.key,
documentation: {
kind: 'markdown',
value: block.text,
},
kind: kindsByType[block.type],
sortText: block.sort.toString(),
filterText: block.text,
insertText: insertNewHash ? '${1:' + block.key + '}$0' : `${block.key}$0`,
insertTextFormat: InsertTextFormat.Snippet,
command: insertNewHash
? {
title: 'Insert Hash',
command: 'foam/insertHash',
arguments: [
{
sourceUri: uri,
sourcePosition: position,
targetUri: targetUri,
targetBlock: block,
},
],
}
: undefined,
};
});
}
function isRequestAtWikiLinkStartBlock(
uri: string,
position: Position
): boolean {
// find the currently open document
const document = textDocumentManager.get(uri);
if (!document) {
console.log('document not found by uri', uri);
return false;
}
// get the preceding three character range
const text = document.getText({
start: { line: position.line, character: position.character - 3 },
end: position,
});
// we get three characters, so that we can trigger autocompletion in following cases:
// 1. when user types the second "[", the range is " [[", and we show all files
// 2. when user has cleared existing link content and types the first character
// of the new content, e.g. "[[S".
return text.includes('[[');
}
// This handler provides the initial list of the completion items.
connection.onCompletion(
(_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
console.log('onCompletion.textDocumentPosition', _textDocumentPosition);
const documentContext = (_textDocumentPosition as any).context;
if (documentContext && documentContext.triggerCharacter === '#') {
return getAvailableSections(
_textDocumentPosition.textDocument.uri,
_textDocumentPosition.position
);
}
if (
isRequestAtWikiLinkStartBlock(
_textDocumentPosition.textDocument.uri,
_textDocumentPosition.position
)
) {
return workspaceFiles.map(document => ({
label: document.title,
detail: document.fileName,
documentation: document.preview,
commitCharacters: ['#', '|'],
kind: CompletionItemKind.Text,
insertTextFormat: InsertTextFormat.Snippet,
textEdit: {
newText: document.target + '$1]] $0',
range: {
start: _textDocumentPosition.position,
end: {
..._textDocumentPosition.position,
character: _textDocumentPosition.position.character + 2,
},
},
},
}));
}
return [];
}
);
connection.onCompletionResolve(item => {
return item;
});
textDocumentManager.listen(connection);
// Listen on the connection
connection.listen();
let activeHashEdit: {
sourceUri: string;
sourceRange: Range;
targetUri: string;
targetRange: Range;
} | null = null;
function siphonOnDidChangeTextDocumentEvent(document: TextDocument, changes: TextDocumentContentChangeEvent[]) {
if (!activeHashEdit) return;
if (document.uri !== activeHashEdit.sourceUri) return;
if (changes.length !== 1) return;
const edit = activeHashEdit;
const text = changes[0].text;
const range = (changes[0] as any).range as Range;
if (!range) return;
if (
range.start.line === range.end.line &&
range.start.line === edit.sourceRange.start.line &&
range.end.line === edit.sourceRange.end.line &&
range.start.character >= edit.sourceRange.start.character &&
range.end.character <= edit.sourceRange.end.character + 1
) {
const val = document.getText(edit.sourceRange);
const hash = val.split(']')[0];
console.log('Text at edit point', hash);
if (range.end.character == edit.sourceRange.end.character + 1) {
edit.sourceRange.end.character += 1;
edit.targetRange.end.character += 1;
activeHashEdit = edit;
}
connection.workspace.applyEdit({
label: 'Update generated hash',
edit: {
changes: {
[edit.targetUri]: [
{
newText: hash.length === 0 ? '' : ` ^${hash}`,
range: edit.targetRange,
},
],
},
},
});
} else {
activeHashEdit = null;
}
};

View File

@@ -0,0 +1,23 @@
import glob from 'glob';
import uriToPath from 'file-uri-to-path';
import { promisify } from 'util';
import { extname, basename, relative } from 'path';
const findAllFiles = promisify(glob);
export async function getWorkspaceFiles(workspaceUri: string) {
let path = uriToPath(workspaceUri);
if (path.substr(-1) === '/') {
path = path.slice(0, -1);
}
const files = await findAllFiles(`${path}/**/*.md`, {});
return files.map(filePath => {
const title = basename(filePath, extname(filePath));
const fileName = relative(path, filePath);
const ext = extname(filePath);
const target = fileName.slice(0, -ext.length);
return { title, filePath, fileName, target, preview: ''};
})
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "es6",
"sourceMap": true,
"inlineSourceMap": false
}
}

View File

@@ -0,0 +1,31 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "src",
"composite": true,
"esModuleInterop": true,
"importHelpers": true,
"module": "CommonJS",
"moduleResolution": "node",
"outDir": "dist",
"rootDir": "./src",
"strict": true,
"lib": [
"esnext"
],
// @NOTE regarding inlineSourceMap:
//
// Preferrably we would use "sourceMap": true to generate
// *.map.js files instead of inline base64 encoded source maps,
// however for some reason the VSCode Node debugger looks
// for source maps in the src/ directory instead of dist/ directory,
// causing the VSCode debugging experience to break.
"inlineSourceMap": true,
},
"include": ["src", "types"],
"exclude": ["node_modules"],
"references": [{
"path": "../foam-core"
}]
}

View File

@@ -0,0 +1 @@
declare module 'remark-wiki-link';

View File

@@ -0,0 +1,4 @@
declare module 'short-hash' {
function shortHash(input: string): string;
export = shortHash;
}

View File

@@ -91,8 +91,8 @@
"build": "tsc -p ./",
"test": "jest",
"lint": "eslint src --ext ts",
"clean": "tsc --build ./tsconfig.json ../foam-core/tsconfig.json --clean",
"watch": "tsc --build ./tsconfig.json ../foam-core/tsconfig.json --watch",
"clean": "tsc --build ./tsconfig.json ../foam-core/tsconfig.json ../foam-language-server/tsconfig.json --clean",
"watch": "tsc --build ./tsconfig.json ../foam-core/tsconfig.json ../foam-language-server/tsconfig.json --watch",
"vscode:start-debugging": "yarn clean && yarn watch",
"vscode:prepublish": "yarn npm-install && yarn run build",
"npm-install": "rimraf node_modules && npm i",
@@ -120,6 +120,7 @@
},
"dependencies": {
"dateformat": "^3.0.3",
"foam-core": "0.3.0"
"foam-core": "0.3.0",
"vscode-languageclient": "^6.1.3"
}
}

View File

@@ -32,8 +32,14 @@ export function activate(context: ExtensionContext) {
});
}
export function deactivate() {
export async function deactivate(): Promise<void> {
workspaceWatcher?.dispose();
// call all deactivate functions and if any of them
// return promises, wait for their completion
await Promise.all(features.map(
f => (f.deactivate && f.deactivate()) || Promise.resolve()
));
}
function isLocalMarkdownFile(uri: Uri) {
@@ -69,7 +75,7 @@ const bootstrap = async (config: FoamConfig) => {
true,
true
);
workspaceWatcher.onDidCreate(uri => {
if (isLocalMarkdownFile(uri)) {
addFile(uri).then(() => {

View File

@@ -1,6 +1,7 @@
import createReferences from "./wikilink-reference-generation";
import openDailyNote from "./open-daily-note";
import janitor from "./janitor";
import languageServerClient from "./language-server-client";
import { FoamFeature } from "../types";
export const features: FoamFeature[] = [createReferences, openDailyNote, janitor];
export const features: FoamFeature[] = [/*createReferences, */languageServerClient, openDailyNote, janitor];

View File

@@ -0,0 +1,74 @@
import * as path from "path";
import { workspace, ExtensionContext } from "vscode";
import { FoamFeature } from "../../types";
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind
} from "vscode-languageclient";
let client: LanguageClient;
const feature: FoamFeature = {
activate: (context: ExtensionContext) => {
// @TODO figure out path for production deploys
let serverModule = context.asAbsolutePath(
path.join("..", "foam-language-server", "dist", "index.js")
);
// The debug options for the server
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] };
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
let serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: {
module: serverModule,
transport: TransportKind.ipc,
options: debugOptions
}
};
// Options to control the language client
let clientOptions: LanguageClientOptions = {
// Register the server for plain markdown documents
documentSelector: [
{ scheme: "file", language: "markdown" },
{ scheme: "file", language: "mdx" }
],
synchronize: {
fileEvents: workspace.createFileSystemWatcher(
"**/.vscode/settings.json"
)
}
};
// Create the language client and start the client.
client = new LanguageClient(
"foam-language-server",
"Foam Language Server",
serverOptions,
clientOptions
);
console.log(
"Starting foam-language-server with options\n",
JSON.stringify(serverOptions, null, 2)
);
// Start the client. This will also launch the server
client.start();
},
deactivate() {
if (client) {
console.log("Stopping foam-language-server");
return client.stop();
}
}
};
export default feature;

View File

@@ -2,5 +2,6 @@ import { ExtensionContext } from "vscode";
import { Foam } from "foam-core";
export interface FoamFeature {
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => void
activate: (context: ExtensionContext, foamPromise: Promise<Foam>) => void,
deactivate?: () => Promise<void>
}

View File

@@ -12,5 +12,7 @@
"exclude": ["node_modules", ".vscode-test"],
"references": [{
"path": "../foam-core"
}, {
"path": "../foam-language-server"
}]
}

119
yarn.lock
View File

@@ -3923,6 +3923,11 @@ char-regex@^1.0.2:
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
character-entities-html4@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125"
integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==
character-entities-legacy@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
@@ -5540,6 +5545,11 @@ file-uri-to-path@1.0.0:
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
file-uri-to-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba"
integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==
fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
@@ -6119,6 +6129,11 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
hash-string@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/hash-string/-/hash-string-1.0.0.tgz#c3fa15f078ddd16bc150b4176fde7091620f2c7f"
integrity sha1-w/oV8Hjd0WvBULQXb95wkWIPLH8=
hosted-git-info@^2.1.4, hosted-git-info@^2.7.1:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
@@ -6448,6 +6463,11 @@ is-alphabetical@^1.0.0:
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==
is-alphanumeric@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4"
integrity sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=
is-alphanumerical@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf"
@@ -6502,7 +6522,7 @@ is-date-object@^1.0.1:
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
is-decimal@^1.0.0:
is-decimal@^1.0.0, is-decimal@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
@@ -8399,6 +8419,11 @@ log-update@^2.3.0:
cli-cursor "^2.0.0"
wrap-ansi "^3.0.1"
longest-streak@^2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4"
integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==
loose-envify@^1.0.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -8521,6 +8546,20 @@ markdown-escapes@^1.0.0:
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==
markdown-table@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b"
integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==
dependencies:
repeat-string "^1.0.0"
mdast-util-compact@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz#cabc69a2f43103628326f35b1acf735d55c99490"
integrity sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA==
dependencies:
unist-util-visit "^2.0.0"
meow@^3.3.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
@@ -10109,6 +10148,26 @@ remark-parse@^8.0.2:
vfile-location "^3.0.0"
xtend "^4.0.1"
remark-stringify@^8.1.1:
version "8.1.1"
resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-8.1.1.tgz#e2a9dc7a7bf44e46a155ec78996db896780d8ce5"
integrity sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A==
dependencies:
ccount "^1.0.0"
is-alphanumeric "^1.0.0"
is-decimal "^1.0.0"
is-whitespace-character "^1.0.0"
longest-streak "^2.0.1"
markdown-escapes "^1.0.0"
markdown-table "^2.0.0"
mdast-util-compact "^2.0.0"
parse-entities "^2.0.0"
repeat-string "^1.5.4"
state-toggle "^1.0.0"
stringify-entities "^3.0.0"
unherit "^1.0.4"
xtend "^4.0.1"
remark-wiki-link@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/remark-wiki-link/-/remark-wiki-link-0.0.4.tgz#9af79538714280513f209661794083d5ae3006d7"
@@ -10127,7 +10186,7 @@ repeat-element@^1.1.2:
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
repeat-string@^1.5.4, repeat-string@^1.6.1:
repeat-string@^1.0.0, repeat-string@^1.5.4, repeat-string@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
@@ -10535,6 +10594,13 @@ shellwords@^0.1.1:
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
short-hash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/short-hash/-/short-hash-1.0.0.tgz#3f491d728fcc777ec605bbaf7f83f23712f42050"
integrity sha1-P0kdco/Md37GBbuvf4PyNxL0IFA=
dependencies:
hash-string "^1.0.0"
side-channel@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947"
@@ -10904,6 +10970,17 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
stringify-entities@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.0.1.tgz#32154b91286ab0869ab2c07696223bd23b6dbfc0"
integrity sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ==
dependencies:
character-entities-html4 "^1.0.0"
character-entities-legacy "^1.0.0"
is-alphanumerical "^1.0.0"
is-decimal "^1.0.2"
is-hexadecimal "^1.0.0"
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -11815,6 +11892,44 @@ vfile@^4.0.0:
unist-util-stringify-position "^2.0.0"
vfile-message "^2.0.0"
vscode-jsonrpc@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794"
integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==
vscode-languageclient@^6.1.3:
version "6.1.3"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz#c979c5bb5855714a0307e998c18ca827c1b3953a"
integrity sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA==
dependencies:
semver "^6.3.0"
vscode-languageserver-protocol "^3.15.3"
vscode-languageserver-protocol@^3.15.3:
version "3.15.3"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb"
integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==
dependencies:
vscode-jsonrpc "^5.0.1"
vscode-languageserver-types "3.15.1"
vscode-languageserver-textdocument@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f"
integrity sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA==
vscode-languageserver-types@3.15.1:
version "3.15.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de"
integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==
vscode-languageserver@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz#d76afc68172c27d4327ee74332b468fbc740d762"
integrity sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ==
dependencies:
vscode-languageserver-protocol "^3.15.3"
vscode-test@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-1.4.0.tgz#a56f73c1667b4d37ba6baa6765f233a19d4ffbfe"