mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-04 18:28:02 +00:00
feat: bootstrap lsp-free features in web (#1105)
* feat: bootstrap lsp-free features in web * ci: update build script * ci: update system build script * dev: touch extension file in web * dev: touch extension file in system * fix: bug import * fix: bug touch
This commit is contained in:
parent
d32f6261f1
commit
d7dd2f30cf
15 changed files with 459 additions and 195 deletions
2
.github/workflows/release-vscode.yml
vendored
2
.github/workflows/release-vscode.yml
vendored
|
@ -409,7 +409,7 @@ jobs:
|
|||
- name: Build tinymist vscode extension
|
||||
run: |
|
||||
yarn
|
||||
yarn run compile
|
||||
yarn run compile:web
|
||||
working-directory: ./editors/vscode
|
||||
- name: Build tinymist library
|
||||
run: yarn build
|
||||
|
|
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -26,7 +26,7 @@
|
|||
"outFiles": [
|
||||
"${workspaceFolder}/editors/vscode/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "VS Code Extension Prelaunch"
|
||||
"preLaunchTask": "VS Code Extension Prelaunch [Web]"
|
||||
},
|
||||
{
|
||||
"name": "Run Extension [Release]",
|
||||
|
|
14
.vscode/tasks.json
vendored
14
.vscode/tasks.json
vendored
|
@ -12,6 +12,13 @@
|
|||
],
|
||||
"dependsOrder": "sequence",
|
||||
},
|
||||
{
|
||||
"label": "VS Code Extension Prelaunch [Web]",
|
||||
"dependsOn": [
|
||||
"Compile VS Code Extension [Web]",
|
||||
],
|
||||
"dependsOrder": "sequence",
|
||||
},
|
||||
{
|
||||
"label": "VS Code Extension Prelaunch [Release]",
|
||||
"dependsOn": [
|
||||
|
@ -41,6 +48,13 @@
|
|||
"path": "editors/vscode",
|
||||
"group": "build",
|
||||
},
|
||||
{
|
||||
"label": "Compile VS Code Extension [Web]",
|
||||
"type": "npm",
|
||||
"script": "compile:web",
|
||||
"path": "editors/vscode",
|
||||
"group": "build",
|
||||
},
|
||||
{
|
||||
"label": "Generate VS Code Extension Bundle",
|
||||
"type": "npm",
|
||||
|
|
16
editors/vscode/esbuild.system.mjs
Normal file
16
editors/vscode/esbuild.system.mjs
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { build } from "esbuild";
|
||||
import * as fs from 'fs';
|
||||
|
||||
if (!fs.existsSync('./out/extension.web.js')) {
|
||||
fs.mkdirSync('./out', { recursive: true });
|
||||
fs.writeFileSync('./out/extension.web.js', '');
|
||||
}
|
||||
|
||||
build({
|
||||
entryPoints: ["./src/extension.ts"],
|
||||
bundle: true,
|
||||
outfile: "./out/extension.js",
|
||||
external: ["vscode"],
|
||||
format: "cjs",
|
||||
platform: "node",
|
||||
}).catch(() => process.exit(1));
|
29
editors/vscode/esbuild.web.mjs
Normal file
29
editors/vscode/esbuild.web.mjs
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { build } from "esbuild";
|
||||
import { polyfillNode } from "esbuild-plugin-polyfill-node";
|
||||
import * as fs from 'fs';
|
||||
|
||||
if (!fs.existsSync('./out/extension.js')) {
|
||||
fs.mkdirSync('./out', { recursive: true });
|
||||
fs.writeFileSync('./out/extension.js', '');
|
||||
}
|
||||
|
||||
build({
|
||||
entryPoints: ["./src/extension.web.ts"],
|
||||
bundle: true,
|
||||
outfile: "./out/extension.web.js",
|
||||
external: ["vscode"],
|
||||
format: "cjs",
|
||||
target: ["es2020", "chrome61", "edge18", "firefox60"],
|
||||
// Node.js global to browser globalThis
|
||||
define: {
|
||||
global: 'globalThis'
|
||||
},
|
||||
plugins: [
|
||||
polyfillNode({
|
||||
polyfills: {
|
||||
crypto: "empty",
|
||||
},
|
||||
// Options (optional)
|
||||
}),
|
||||
],
|
||||
}).catch(() => process.exit(1));
|
|
@ -1095,12 +1095,14 @@
|
|||
"scripts": {
|
||||
"build:frontend": "cd ../../ && yarn build:preview && yarn build:editor-tools",
|
||||
"build:syntax": "cd ../../syntaxes/textmate && yarn run compile && yarn run bundle",
|
||||
"build-web-base": "esbuild ./src/extension.web.ts --bundle --outfile=out/extension.web.js --external:vscode --format=cjs --target=es2020,chrome58,edge16,firefox57",
|
||||
"build-system-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node",
|
||||
"build-web-base": "node esbuild.web.mjs",
|
||||
"build-system-base": "node esbuild.system.mjs",
|
||||
"build-base": "yarn run build-web-base && yarn run build-system-base",
|
||||
"vscode:prepublish": "yarn run build-base -- --minify && yarn run build:frontend && node scripts/check-version.mjs && node scripts/postinstall.cjs && node scripts/config-man.cjs",
|
||||
"compile-shared": "yarn run build:syntax && yarn run build:frontend && node scripts/check-version.mjs && node scripts/postinstall.cjs && node scripts/config-man.cjs",
|
||||
"compile:web": "yarn run build-web-base -- --minify && yarn run compile-shared",
|
||||
"compile:system": "yarn run build-system-base -- --minify && yarn run compile-shared",
|
||||
"package": "vsce package --yarn",
|
||||
"compile": "yarn run build-system-base -- --sourcemap && yarn run build:syntax && yarn run build:frontend && node scripts/postinstall.cjs",
|
||||
"compile": "yarn run compile:system",
|
||||
"watch": "yarn run build-system-base -- --sourcemap --watch",
|
||||
"check": "tsc --noEmit",
|
||||
"lint": "eslint ./src --ext .ts",
|
||||
|
@ -1110,22 +1112,22 @@
|
|||
"test": "rimraf test-dist/ && tsc -p tsconfig.test.json && node test-dist/test/runTests.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "^9.0.0",
|
||||
"cpr": "^3.0.1",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"vscode-languageclient": "^9.0.0",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.16",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^20.8.10",
|
||||
"@types/vscode": "^1.82.0",
|
||||
"@types/chai": "^4.3.16",
|
||||
"@types/ws": "^8.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
||||
"@typescript-eslint/parser": "^6.9.1",
|
||||
"@types/ws": "^8.5.5",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@vscode/vsce": "^2.22.0",
|
||||
"@vscode/test-electron": "^2.3.9",
|
||||
"mocha": "^10.2.0",
|
||||
"@vscode/vsce": "^2.22.0",
|
||||
"chai": "^5.1.1",
|
||||
"esbuild": "^0.19.5",
|
||||
"eslint": "^8.52.0",
|
||||
|
@ -1133,6 +1135,7 @@
|
|||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-n": "^16.2.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"mocha": "^10.2.0",
|
||||
"ovsx": "^0.8.3",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
|
|
147
editors/vscode/src/extension.shared.ts
Normal file
147
editors/vscode/src/extension.shared.ts
Normal file
|
@ -0,0 +1,147 @@
|
|||
import { type ExtensionContext, commands } from "vscode";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { loadTinymistConfig } from "./config";
|
||||
import { tinymist } from "./lsp";
|
||||
import { extensionState } from "./state";
|
||||
|
||||
import { previewPreload } from "./features/preview";
|
||||
import { onEnterHandler } from "./lsp.on-enter";
|
||||
|
||||
/**
|
||||
* The condition
|
||||
*/
|
||||
type FeatureCondition = boolean;
|
||||
/**
|
||||
* The initialization vector
|
||||
*/
|
||||
type ActivationVector = (context: ExtensionContext) => void;
|
||||
/**
|
||||
* The initialization vector
|
||||
*/
|
||||
type DeactivationVector = (context: ExtensionContext) => void;
|
||||
/**
|
||||
* The feature entry. A conditional feature activation vector is required
|
||||
* and an optional deactivation vector is also supported.
|
||||
*/
|
||||
export type FeatureEntry =
|
||||
| [FeatureCondition, ActivationVector]
|
||||
| [FeatureCondition, ActivationVector, DeactivationVector];
|
||||
|
||||
function configureEditorAndLanguage(context: ExtensionContext, trait: TinymistTrait) {
|
||||
const isDevMode = vscode.ExtensionMode.Development == context.extensionMode;
|
||||
const isWeb = extensionState.features.web;
|
||||
const { config } = trait;
|
||||
|
||||
// Inform server that we support named completion callback at the client side
|
||||
config.triggerSuggest = true;
|
||||
config.triggerSuggestAndParameterHints = true;
|
||||
config.triggerParameterHints = true;
|
||||
config.supportHtmlInMarkdown = true;
|
||||
// Sets shared features
|
||||
extensionState.features.preview = !isWeb && config.previewFeature === "enable";
|
||||
extensionState.features.wordSeparator = config.configureDefaultWordSeparator !== "disable";
|
||||
extensionState.features.devKit = isDevMode || config.devKit === "enable";
|
||||
extensionState.features.dragAndDrop = !isWeb && config.dragAndDrop === "enable";
|
||||
extensionState.features.onEnter = !isWeb && !!config.onEnterEvent;
|
||||
extensionState.features.renderDocs = !isWeb && config.renderDocs === "enable";
|
||||
|
||||
// Configures advanced editor settings to affect the host process
|
||||
let configWordSeparators = async () => {
|
||||
const wordSeparators = "`~!@#$%^&*()=+[{]}\\|;:'\",.<>/?";
|
||||
const config1 = vscode.workspace.getConfiguration("", { languageId: "typst" });
|
||||
await config1.update("editor.wordSeparators", wordSeparators, true, true);
|
||||
const config2 = vscode.workspace.getConfiguration("", { languageId: "typst-code" });
|
||||
await config2.update("editor.wordSeparators", wordSeparators, true, true);
|
||||
};
|
||||
// Runs configuration asynchronously to avoid blocking the activation
|
||||
if (extensionState.features.wordSeparator) {
|
||||
configWordSeparators().catch((e) =>
|
||||
console.error("cannot change editor.wordSeparators for typst", e),
|
||||
);
|
||||
} else {
|
||||
// console.log("skip configuring word separator on startup");
|
||||
}
|
||||
|
||||
// Configures advanced language configuration
|
||||
tinymist.configureLanguage(config["typingContinueCommentsOnNewline"]);
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration("tinymist.typingContinueCommentsOnNewline")) {
|
||||
const config = loadTinymistConfig();
|
||||
// Update language configuration
|
||||
tinymist.configureLanguage(config["typingContinueCommentsOnNewline"]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
interface TinymistTrait {
|
||||
activateTable(): FeatureEntry[];
|
||||
config: Record<string, any>;
|
||||
}
|
||||
|
||||
export async function tinymistActivate(
|
||||
context: ExtensionContext,
|
||||
trait: TinymistTrait,
|
||||
): Promise<void> {
|
||||
const { activateTable, config } = trait;
|
||||
tinymist.context = context;
|
||||
|
||||
// Sets a global context key to indicate that the extension is activated
|
||||
vscode.commands.executeCommand("setContext", "ext.tinymistActivated", true);
|
||||
context.subscriptions.push({
|
||||
dispose: () => {
|
||||
vscode.commands.executeCommand("setContext", "ext.tinymistActivated", false);
|
||||
},
|
||||
});
|
||||
|
||||
configureEditorAndLanguage(context, trait);
|
||||
|
||||
// Initializes language client
|
||||
if (extensionState.features.lsp) {
|
||||
tinymist.initClient(config);
|
||||
}
|
||||
// Register Shared commands
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("tinymist.onEnter", onEnterHandler),
|
||||
commands.registerCommand("tinymist.restartServer", async () => {
|
||||
await tinymistDeactivate(trait);
|
||||
await tinymistActivate(context, trait);
|
||||
}),
|
||||
commands.registerCommand("tinymist.showLog", () => tinymist.showLog()),
|
||||
);
|
||||
// Activates platform-dependent features
|
||||
for (const [condition, activate] of activateTable()) {
|
||||
if (condition) {
|
||||
activate(context);
|
||||
}
|
||||
}
|
||||
// Starts language client
|
||||
if (extensionState.features.lsp) {
|
||||
await tinymist.startClient();
|
||||
}
|
||||
// Loads the preview HTML from the binary
|
||||
if (extensionState.features.lsp && extensionState.features.preview) {
|
||||
previewPreload(context);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
export async function tinymistDeactivate(
|
||||
trait: Pick<TinymistTrait, "activateTable">,
|
||||
): Promise<void> {
|
||||
for (const [condition, deactivate] of trait.activateTable()) {
|
||||
if (condition) {
|
||||
deactivate(tinymist.context);
|
||||
}
|
||||
}
|
||||
if (tinymist.context) {
|
||||
for (const disposable of tinymist.context.subscriptions.splice(0)) {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
await tinymist.stop();
|
||||
tinymist.context = undefined!;
|
||||
}
|
|
@ -14,24 +14,41 @@ import { loadTinymistConfig } from "./config";
|
|||
import { triggerStatusBar } from "./ui-extends";
|
||||
import { commandCreateLocalPackage, commandOpenLocalPackage } from "./package-manager";
|
||||
import { activeTypstEditor } from "./util";
|
||||
import { tinymist } from "./lsp";
|
||||
import { onEnterHandler } from "./lsp.on-enter";
|
||||
import { LanguageState, tinymist } from "./lsp";
|
||||
import { extensionState } from "./state";
|
||||
|
||||
import { getUserPackageData } from "./features/tool";
|
||||
import { SymbolViewProvider } from "./features/tool.symbol-view";
|
||||
import { setIsTinymist as previewSetIsTinymist } from "./features/preview-compat";
|
||||
import { previewActivate, previewDeactivate, previewPreload } from "./features/preview";
|
||||
import { previewActivate, previewDeactivate } from "./features/preview";
|
||||
import { taskActivate } from "./features/tasks";
|
||||
import { devKitFeatureActivate } from "./features/dev-kit";
|
||||
import { labelFeatureActivate } from "./features/label";
|
||||
import { packageFeatureActivate } from "./features/package";
|
||||
import { toolFeatureActivate } from "./features/tool";
|
||||
import { dragAndDropActivate } from "./features/drag-and-drop";
|
||||
import { FeatureEntry, tinymistActivate, tinymistDeactivate } from "./extension.shared";
|
||||
import { LanguageClient } from "vscode-languageclient/node";
|
||||
|
||||
LanguageState.Client = LanguageClient;
|
||||
|
||||
const systemActivateTable = (): FeatureEntry[] => [
|
||||
[extensionState.features.label, labelFeatureActivate],
|
||||
[extensionState.features.package, packageFeatureActivate],
|
||||
[extensionState.features.tool, toolFeatureActivate],
|
||||
[extensionState.features.dragAndDrop, dragAndDropActivate],
|
||||
[extensionState.features.task, taskActivate],
|
||||
[extensionState.features.devKit, devKitFeatureActivate],
|
||||
[extensionState.features.preview, previewActivateInTinymist, previewDeactivate],
|
||||
[extensionState.features.language, languageActivate],
|
||||
];
|
||||
|
||||
export async function activate(context: ExtensionContext): Promise<void> {
|
||||
try {
|
||||
return await doActivate(context);
|
||||
return await tinymistActivate(context, {
|
||||
activateTable: systemActivateTable,
|
||||
config: loadTinymistConfig(),
|
||||
});
|
||||
} catch (e) {
|
||||
void window.showErrorMessage(`Failed to activate tinymist: ${e}`);
|
||||
throw e;
|
||||
|
@ -39,109 +56,32 @@ export async function activate(context: ExtensionContext): Promise<void> {
|
|||
}
|
||||
|
||||
export async function deactivate(): Promise<void> {
|
||||
// Remove handlers first to avoid sending messages to the server when deactivating
|
||||
previewDeactivate();
|
||||
if (tinymist.context) {
|
||||
for (const disposable of tinymist.context.subscriptions.splice(0)) {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
await tinymist.stop();
|
||||
tinymist.context = undefined!;
|
||||
tinymistDeactivate({
|
||||
activateTable: systemActivateTable,
|
||||
});
|
||||
}
|
||||
|
||||
export async function doActivate(context: ExtensionContext): Promise<void> {
|
||||
tinymist.context = context;
|
||||
const isDevMode = vscode.ExtensionMode.Development == context.extensionMode;
|
||||
// Sets a global context key to indicate that the extension is activated
|
||||
vscode.commands.executeCommand("setContext", "ext.tinymistActivated", true);
|
||||
context.subscriptions.push({
|
||||
dispose: () => {
|
||||
vscode.commands.executeCommand("setContext", "ext.tinymistActivated", false);
|
||||
},
|
||||
});
|
||||
// Loads configuration
|
||||
const config = loadTinymistConfig();
|
||||
// Inform server that we support named completion callback at the client side
|
||||
config.triggerSuggest = true;
|
||||
config.triggerSuggestAndParameterHints = true;
|
||||
config.triggerParameterHints = true;
|
||||
config.supportHtmlInMarkdown = true;
|
||||
// Sets features
|
||||
extensionState.features.preview = config.previewFeature === "enable";
|
||||
extensionState.features.wordSeparator = config.configureDefaultWordSeparator !== "disable";
|
||||
extensionState.features.devKit = isDevMode || config.devKit === "enable";
|
||||
extensionState.features.dragAndDrop = config.dragAndDrop === "enable";
|
||||
extensionState.features.onEnter = !!config.onEnterEvent;
|
||||
extensionState.features.renderDocs = config.renderDocs === "enable";
|
||||
|
||||
// Configures advanced editor settings to affect the host process
|
||||
let configWordSeparators = async () => {
|
||||
const wordSeparators = "`~!@#$%^&*()=+[{]}\\|;:'\",.<>/?";
|
||||
const config1 = vscode.workspace.getConfiguration("", { languageId: "typst" });
|
||||
await config1.update("editor.wordSeparators", wordSeparators, true, true);
|
||||
const config2 = vscode.workspace.getConfiguration("", { languageId: "typst-code" });
|
||||
await config2.update("editor.wordSeparators", wordSeparators, true, true);
|
||||
};
|
||||
// Runs configuration asynchronously to avoid blocking the activation
|
||||
if (extensionState.features.wordSeparator) {
|
||||
configWordSeparators().catch((e) =>
|
||||
console.error("cannot change editor.wordSeparators for typst", e),
|
||||
function previewActivateInTinymist(context: ExtensionContext) {
|
||||
const typstPreviewExtension = vscode.extensions.getExtension("mgt19937.typst-preview");
|
||||
if (typstPreviewExtension) {
|
||||
void vscode.window.showWarningMessage(
|
||||
"Tinymist Says:\n\nTypst Preview extension is already integrated into Tinymist. Please disable Typst Preview extension to avoid conflicts.",
|
||||
);
|
||||
} else {
|
||||
// console.log("skip configuring word separator on startup");
|
||||
}
|
||||
|
||||
// Configures advanced language configuration
|
||||
tinymist.configureLanguage(config["typingContinueCommentsOnNewline"]);
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration("tinymist.typingContinueCommentsOnNewline")) {
|
||||
const config = loadTinymistConfig();
|
||||
// Update language configuration
|
||||
tinymist.configureLanguage(config["typingContinueCommentsOnNewline"]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
// Tests compat-mode preview extension
|
||||
// previewActivate(context, true);
|
||||
|
||||
// Initializes language client
|
||||
const client = tinymist.initClient(config);
|
||||
// Activates features
|
||||
labelFeatureActivate(context);
|
||||
packageFeatureActivate(context);
|
||||
toolFeatureActivate(context);
|
||||
if (extensionState.features.dragAndDrop) {
|
||||
dragAndDropActivate(context);
|
||||
}
|
||||
if (extensionState.features.task) {
|
||||
taskActivate(context);
|
||||
}
|
||||
if (extensionState.features.devKit) {
|
||||
devKitFeatureActivate(context);
|
||||
}
|
||||
if (extensionState.features.preview) {
|
||||
const typstPreviewExtension = vscode.extensions.getExtension("mgt19937.typst-preview");
|
||||
if (typstPreviewExtension) {
|
||||
void vscode.window.showWarningMessage(
|
||||
"Tinymist Says:\n\nTypst Preview extension is already integrated into Tinymist. Please disable Typst Preview extension to avoid conflicts.",
|
||||
);
|
||||
}
|
||||
// Runs Integrated preview extension
|
||||
previewSetIsTinymist();
|
||||
previewActivate(context, false);
|
||||
}
|
||||
|
||||
// Tests compat-mode preview extension
|
||||
// previewActivate(context, true);
|
||||
|
||||
// Runs Integrated preview extension
|
||||
previewSetIsTinymist();
|
||||
previewActivate(context, false);
|
||||
}
|
||||
|
||||
// Starts language client
|
||||
languageActivate(context);
|
||||
await tinymist.startClient();
|
||||
|
||||
// Loads the preview HTML from the binary
|
||||
if (extensionState.features.preview) {
|
||||
previewPreload(context);
|
||||
async function languageActivate(context: ExtensionContext) {
|
||||
const client = tinymist.client;
|
||||
if (!client) {
|
||||
console.warn("activating language feature without starting the tinymist language server");
|
||||
return;
|
||||
}
|
||||
|
||||
// Watch all non typst files.
|
||||
|
@ -209,10 +149,6 @@ export async function doActivate(context: ExtensionContext): Promise<void> {
|
|||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async function languageActivate(context: ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
window.onDidChangeActiveTextEditor((editor: TextEditor | undefined) => {
|
||||
if (editor?.document.isUntitled) {
|
||||
|
@ -257,7 +193,6 @@ async function languageActivate(context: ExtensionContext) {
|
|||
|
||||
// prettier-ignore
|
||||
context.subscriptions.push(
|
||||
commands.registerCommand("tinymist.onEnter", onEnterHandler),
|
||||
commands.registerCommand("tinymist.openInternal", openInternal),
|
||||
commands.registerCommand("tinymist.openExternal", openExternal),
|
||||
|
||||
|
@ -265,12 +200,7 @@ async function languageActivate(context: ExtensionContext) {
|
|||
commands.registerCommand("tinymist.showPdf", () => commandShow("Pdf")),
|
||||
commands.registerCommand("tinymist.getCurrentDocumentMetrics", commandGetCurrentDocumentMetrics),
|
||||
commands.registerCommand("tinymist.clearCache", commandClearCache),
|
||||
commands.registerCommand("tinymist.restartServer", async () => {
|
||||
await deactivate();
|
||||
await doActivate(context);
|
||||
}),
|
||||
commands.registerCommand("tinymist.runCodeLens", commandRunCodeLens),
|
||||
commands.registerCommand("tinymist.showLog", () => tinymist.showLog()),
|
||||
commands.registerCommand("tinymist.copyAnsiHighlight", commandCopyAnsiHighlight),
|
||||
|
||||
commands.registerCommand("tinymist.pinMainToCurrent", () => commandPinMain(true)),
|
||||
|
|
|
@ -1,5 +1,40 @@
|
|||
import { ExtensionContext } from "vscode";
|
||||
import { ExtensionContext, window } from "vscode";
|
||||
import { loadTinymistConfig } from "./config";
|
||||
import { tinymistActivate, tinymistDeactivate } from "./extension.shared";
|
||||
import { extensionState } from "./state";
|
||||
|
||||
export async function activate(context: ExtensionContext): Promise<void> {}
|
||||
const webActivateTable = () => [];
|
||||
|
||||
export async function deactivate(): Promise<void> {}
|
||||
export async function activate(context: ExtensionContext): Promise<void> {
|
||||
extensionState.features = {
|
||||
web: true,
|
||||
lsp: false,
|
||||
task: false,
|
||||
wordSeparator: true,
|
||||
label: false,
|
||||
package: false,
|
||||
tool: false,
|
||||
devKit: false,
|
||||
dragAndDrop: false,
|
||||
onEnter: false,
|
||||
preview: false,
|
||||
language: false,
|
||||
renderDocs: false,
|
||||
};
|
||||
|
||||
try {
|
||||
return await tinymistActivate(context, {
|
||||
activateTable: webActivateTable,
|
||||
config: loadTinymistConfig(),
|
||||
});
|
||||
} catch (e) {
|
||||
void window.showErrorMessage(`Failed to activate tinymist: ${e}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deactivate(): Promise<void> {
|
||||
tinymistDeactivate({
|
||||
activateTable: webActivateTable,
|
||||
});
|
||||
}
|
||||
|
|
61
editors/vscode/src/features/hover-storage.tmp.ts
Normal file
61
editors/vscode/src/features/hover-storage.tmp.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Hover storage backed by a temporary directory in the file system
|
||||
|
||||
import * as vscode from "vscode";
|
||||
import * as crypto from "crypto";
|
||||
import { Uri } from "vscode";
|
||||
import { base64Decode } from "../util";
|
||||
import { HoverStorageDummyHandler } from "./hover-storage";
|
||||
|
||||
export class HoverTmpStorage {
|
||||
constructor(readonly context: vscode.ExtensionContext) {}
|
||||
|
||||
async startHover() {
|
||||
// This is a "workspace wide" storage for temporary hover images
|
||||
if (this.context.storageUri) {
|
||||
const tmpImageDir = Uri.joinPath(this.context.storageUri, "tmp/hover-images/");
|
||||
try {
|
||||
const previousEntries = await vscode.workspace.fs.readDirectory(tmpImageDir);
|
||||
let deleted = 0;
|
||||
for (const [name, type] of previousEntries) {
|
||||
if (type === vscode.FileType.File) {
|
||||
deleted++;
|
||||
await vscode.workspace.fs.delete(Uri.joinPath(tmpImageDir, name));
|
||||
}
|
||||
}
|
||||
if (deleted > 0) {
|
||||
console.log(`Deleted ${deleted} hover images`);
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
await vscode.workspace.fs.createDirectory(tmpImageDir);
|
||||
return new HoverStorageTmpFsHandler(Uri.joinPath(this.context.storageUri, "tmp/"));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return new HoverStorageDummyHandler();
|
||||
}
|
||||
}
|
||||
|
||||
class HoverStorageTmpFsHandler {
|
||||
promises: PromiseLike<void>[] = [];
|
||||
|
||||
constructor(readonly _baseUri: vscode.Uri) {}
|
||||
|
||||
baseUri() {
|
||||
return this._baseUri;
|
||||
}
|
||||
|
||||
storeImage(content: string) {
|
||||
const fs = vscode.workspace.fs;
|
||||
const hash = crypto.createHash("sha256").update(content).digest("hex");
|
||||
const tmpImagePath = `./hover-images/${hash}.svg`;
|
||||
const output = Uri.joinPath(this._baseUri, tmpImagePath);
|
||||
const outputContent = base64Decode(content);
|
||||
this.promises.push(fs.writeFile(output, Buffer.from(outputContent, "utf-8")));
|
||||
return tmpImagePath;
|
||||
}
|
||||
|
||||
async finish() {
|
||||
await Promise.all(this.promises);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import * as vscode from "vscode";
|
||||
import * as crypto from "crypto";
|
||||
import { Uri } from "vscode";
|
||||
import { base64Decode } from "../util";
|
||||
|
||||
export interface HoverStorageProvider {
|
||||
new (context: vscode.ExtensionContext): HoverStorage;
|
||||
}
|
||||
|
||||
export interface HoverStorage {
|
||||
startHover(): Promise<HoverStorageHandler>;
|
||||
|
@ -19,61 +20,7 @@ export class HoverDummyStorage {
|
|||
}
|
||||
}
|
||||
|
||||
export class HoverTmpStorage {
|
||||
constructor(readonly context: vscode.ExtensionContext) {}
|
||||
|
||||
async startHover() {
|
||||
// This is a "workspace wide" storage for temporary hover images
|
||||
if (this.context.storageUri) {
|
||||
const tmpImageDir = Uri.joinPath(this.context.storageUri, "tmp/hover-images/");
|
||||
try {
|
||||
const previousEntries = await vscode.workspace.fs.readDirectory(tmpImageDir);
|
||||
let deleted = 0;
|
||||
for (const [name, type] of previousEntries) {
|
||||
if (type === vscode.FileType.File) {
|
||||
deleted++;
|
||||
await vscode.workspace.fs.delete(Uri.joinPath(tmpImageDir, name));
|
||||
}
|
||||
}
|
||||
if (deleted > 0) {
|
||||
console.log(`Deleted ${deleted} hover images`);
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
await vscode.workspace.fs.createDirectory(tmpImageDir);
|
||||
return new HoverStorageTmpFsHandler(Uri.joinPath(this.context.storageUri, "tmp/"));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return new HoverStorageDummyHandler();
|
||||
}
|
||||
}
|
||||
|
||||
class HoverStorageTmpFsHandler {
|
||||
promises: PromiseLike<void>[] = [];
|
||||
|
||||
constructor(readonly _baseUri: vscode.Uri) {}
|
||||
|
||||
baseUri() {
|
||||
return this._baseUri;
|
||||
}
|
||||
|
||||
storeImage(content: string) {
|
||||
const fs = vscode.workspace.fs;
|
||||
const hash = crypto.createHash("sha256").update(content).digest("hex");
|
||||
const tmpImagePath = `./hover-images/${hash}.svg`;
|
||||
const output = Uri.joinPath(this._baseUri, tmpImagePath);
|
||||
const outputContent = base64Decode(content);
|
||||
this.promises.push(fs.writeFile(output, Buffer.from(outputContent, "utf-8")));
|
||||
return tmpImagePath;
|
||||
}
|
||||
|
||||
async finish() {
|
||||
await Promise.all(this.promises);
|
||||
}
|
||||
}
|
||||
|
||||
class HoverStorageDummyHandler {
|
||||
export class HoverStorageDummyHandler {
|
||||
baseUri() {
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -3,14 +3,15 @@ import { resolve } from "path";
|
|||
|
||||
import * as vscode from "vscode";
|
||||
import { ExtensionMode } from "vscode";
|
||||
import {
|
||||
import type {
|
||||
LanguageClient,
|
||||
SymbolInformation,
|
||||
type LanguageClientOptions,
|
||||
type ServerOptions,
|
||||
LanguageClientOptions,
|
||||
ServerOptions,
|
||||
} from "vscode-languageclient/node";
|
||||
|
||||
import { HoverDummyStorage, HoverTmpStorage } from "./features/hover-storage";
|
||||
import { HoverDummyStorage } from "./features/hover-storage";
|
||||
import type { HoverTmpStorage } from "./features/hover-storage.tmp";
|
||||
import { extensionState } from "./state";
|
||||
import { DisposeList, getSensibleTextEditorColumn, typstDocumentSelector } from "./util";
|
||||
import { substVscodeVarsInConfig } from "./config";
|
||||
|
@ -86,7 +87,10 @@ interface JumpInfo {
|
|||
end: [number, number] | null;
|
||||
}
|
||||
|
||||
class LanguageState {
|
||||
export class LanguageState {
|
||||
static Client: typeof LanguageClient = undefined!;
|
||||
static HoverTmpStorage?: typeof HoverTmpStorage = undefined;
|
||||
|
||||
outputChannel: vscode.OutputChannel = vscode.window.createOutputChannel("Tinymist Typst", "log");
|
||||
context: vscode.ExtensionContext = undefined!;
|
||||
client: LanguageClient | undefined = undefined;
|
||||
|
@ -180,9 +184,10 @@ class LanguageState {
|
|||
const trustedCommands = {
|
||||
enabledCommands: ["tinymist.openInternal", "tinymist.openExternal"],
|
||||
};
|
||||
const hoverStorage = extensionState.features.renderDocs
|
||||
? new HoverTmpStorage(context)
|
||||
: new HoverDummyStorage();
|
||||
const hoverStorage =
|
||||
extensionState.features.renderDocs && LanguageState.HoverTmpStorage
|
||||
? new LanguageState.HoverTmpStorage(context)
|
||||
: new HoverDummyStorage();
|
||||
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
documentSelector: typstDocumentSelector,
|
||||
|
@ -230,7 +235,7 @@ class LanguageState {
|
|||
},
|
||||
};
|
||||
|
||||
const client = (this.client = new LanguageClient(
|
||||
const client = (this.client = new LanguageState.Client(
|
||||
"tinymist",
|
||||
"Tinymist Typst Language Server",
|
||||
serverOptions,
|
||||
|
|
|
@ -4,12 +4,18 @@ export type ExtensionContext = vscode.ExtensionContext;
|
|||
|
||||
interface ExtensionState {
|
||||
features: {
|
||||
web: boolean;
|
||||
lsp: boolean;
|
||||
task: boolean;
|
||||
devKit: boolean;
|
||||
wordSeparator: boolean;
|
||||
dragAndDrop: boolean;
|
||||
label: boolean;
|
||||
package: boolean;
|
||||
tool: boolean;
|
||||
onEnter: boolean;
|
||||
preview: boolean;
|
||||
language: boolean;
|
||||
renderDocs: boolean;
|
||||
};
|
||||
mut: {
|
||||
|
@ -22,12 +28,18 @@ interface ExtensionState {
|
|||
|
||||
export const extensionState: ExtensionState = {
|
||||
features: {
|
||||
web: false,
|
||||
lsp: true,
|
||||
task: true,
|
||||
wordSeparator: true,
|
||||
label: true,
|
||||
package: true,
|
||||
tool: true,
|
||||
devKit: false,
|
||||
dragAndDrop: false,
|
||||
onEnter: false,
|
||||
preview: false,
|
||||
language: true,
|
||||
renderDocs: false,
|
||||
},
|
||||
mut: {
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
import vscode = require("vscode");
|
||||
import process = require("process");
|
||||
import path = require("path");
|
||||
import { extensionState } from "./state";
|
||||
|
||||
export function vscodeVariables(
|
||||
string: string,
|
||||
recursive?: boolean,
|
||||
context = new CodeVariableContext()
|
||||
context = new CodeVariableContext(),
|
||||
): string {
|
||||
while (true) {
|
||||
string = string.replace(context.regex, (match) => {
|
||||
|
@ -56,8 +57,12 @@ export class CodeVariableContext {
|
|||
env: {
|
||||
variable: true,
|
||||
value: (variable: string) => {
|
||||
if (extensionState.features.web) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const e = variable.match(/\${env:(.*?)}/);
|
||||
return (e && process.env[e[1]]) || "";
|
||||
return (e && process.env?.[e[1]]) || "";
|
||||
},
|
||||
},
|
||||
config: {
|
||||
|
@ -162,7 +167,7 @@ export class CodeVariableContext {
|
|||
const activeTextEditor = this.activeTextEditor;
|
||||
if (activeTextEditor) {
|
||||
selectedText = activeTextEditor.document.getText(
|
||||
new vscode.Range(activeTextEditor.selection.start, activeTextEditor.selection.end)
|
||||
new vscode.Range(activeTextEditor.selection.start, activeTextEditor.selection.end),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
60
yarn.lock
60
yarn.lock
|
@ -408,6 +408,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
|
||||
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
|
||||
|
||||
"@jspm/core@^2.0.1":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@jspm/core/-/core-2.1.0.tgz#ee21ff64591d68de98b79ca8e4bd6c5249fded53"
|
||||
integrity sha512-3sRl+pkyFY/kLmHl0cgHiFp2xEqErA8N3ECjMs7serSUBmoJ70lBa0PG5t0IM6WJgdZNyyI0R8YFfi5wM8+mzg==
|
||||
|
||||
"@myriaddreamin/typst-ts-renderer@0.5.1":
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@myriaddreamin/typst-ts-renderer/-/typst-ts-renderer-0.5.1.tgz#951bb1df75c93c29b1072b71fe375fb030a8bde2"
|
||||
|
@ -1708,6 +1713,14 @@ es-to-primitive@^1.2.1:
|
|||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.2"
|
||||
|
||||
esbuild-plugin-polyfill-node@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-plugin-polyfill-node/-/esbuild-plugin-polyfill-node-0.3.0.tgz#e7e3804b8272df51ae4f8ebfb7445a03712504cb"
|
||||
integrity sha512-SHG6CKUfWfYyYXGpW143NEZtcVVn8S/WHcEOxk62LuDXnY4Zpmc+WmxJKN6GMTgTClXJXhEM5KQlxKY6YjbucQ==
|
||||
dependencies:
|
||||
"@jspm/core" "^2.0.1"
|
||||
import-meta-resolve "^3.0.0"
|
||||
|
||||
esbuild@^0.18.10:
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6"
|
||||
|
@ -2388,6 +2401,11 @@ import-fresh@^3.2.1:
|
|||
parent-module "^1.0.0"
|
||||
resolve-from "^4.0.0"
|
||||
|
||||
import-meta-resolve@^3.0.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz#75d194ae465d17c15736f414734310c87d4c45d7"
|
||||
integrity sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==
|
||||
|
||||
imurmurhash@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
|
@ -3985,6 +4003,48 @@ tunnel@0.0.6:
|
|||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
||||
|
||||
turbo-darwin-64@2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-2.3.3.tgz#875975cddf7abdb52b28e9cab234fc73ca001e80"
|
||||
integrity sha512-bxX82xe6du/3rPmm4aCC5RdEilIN99VUld4HkFQuw+mvFg6darNBuQxyWSHZTtc25XgYjQrjsV05888w1grpaA==
|
||||
|
||||
turbo-darwin-arm64@2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-2.3.3.tgz#5e85d6dadac6560782bb91081fee56f9cfcd103e"
|
||||
integrity sha512-DYbQwa3NsAuWkCUYVzfOUBbSUBVQzH5HWUFy2Kgi3fGjIWVZOFk86ss+xsWu//rlEAfYwEmopigsPYSmW4X15A==
|
||||
|
||||
turbo-linux-64@2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-2.3.3.tgz#634f2053cff6ff056bec7f307c2fb4dd9a2bc86f"
|
||||
integrity sha512-eHj9OIB0dFaP6BxB88jSuaCLsOQSYWBgmhy2ErCu6D2GG6xW3b6e2UWHl/1Ho9FsTg4uVgo4DB9wGsKa5erjUA==
|
||||
|
||||
turbo-linux-arm64@2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-2.3.3.tgz#284e26825f5d692bffb5b126a9aeccaae6a4533b"
|
||||
integrity sha512-NmDE/NjZoDj1UWBhMtOPmqFLEBKhzGS61KObfrDEbXvU3lekwHeoPvAMfcovzswzch+kN2DrtbNIlz+/rp8OCg==
|
||||
|
||||
turbo-windows-64@2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-2.3.3.tgz#2900ac2c00d9609bc480d90564a98ca1cc7f4b17"
|
||||
integrity sha512-O2+BS4QqjK3dOERscXqv7N2GXNcqHr9hXumkMxDj/oGx9oCatIwnnwx34UmzodloSnJpgSqjl8iRWiY65SmYoQ==
|
||||
|
||||
turbo-windows-arm64@2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-2.3.3.tgz#395508cdf6b351d7dd324fb0b318c1b2cf1d66dd"
|
||||
integrity sha512-dW4ZK1r6XLPNYLIKjC4o87HxYidtRRcBeo/hZ9Wng2XM/MqqYkAyzJXJGgRMsc0MMEN9z4+ZIfnSNBrA0b08ag==
|
||||
|
||||
turbo@^2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.3.3.tgz#70736263c75f7c0c501278214dee49859e1c7fcd"
|
||||
integrity sha512-DUHWQAcC8BTiUZDRzAYGvpSpGLiaOQPfYXlCieQbwUvmml/LRGIe3raKdrOPOoiX0DYlzxs2nH6BoWJoZrj8hA==
|
||||
optionalDependencies:
|
||||
turbo-darwin-64 "2.3.3"
|
||||
turbo-darwin-arm64 "2.3.3"
|
||||
turbo-linux-64 "2.3.3"
|
||||
turbo-linux-arm64 "2.3.3"
|
||||
turbo-windows-64 "2.3.3"
|
||||
turbo-windows-arm64 "2.3.3"
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue