From 4f20e30f4cf2809981bb529b94ef8336d23ffb3e Mon Sep 17 00:00:00 2001 From: _Kerman Date: Sat, 13 Jul 2024 18:09:32 +0800 Subject: [PATCH] refactor(vscode): migrate to `reactive-vscode` (#1673) --- packages/vscode/README.md | 1 + packages/vscode/package.json | 10 +-- packages/vscode/src/commands.ts | 62 ++++++------- .../src/composables/useActiveTextEditor.ts | 19 ---- .../vscode/src/composables/useDevServer.ts | 16 ++-- .../vscode/src/composables/useDisposable.ts | 7 -- .../vscode/src/composables/useDocumentText.ts | 20 ----- .../src/composables/useEditingSlideSource.ts | 21 ++--- .../src/composables/useFocusedSlideNo.ts | 23 ++--- .../vscode/src/composables/usePreviewState.ts | 8 +- .../src/composables/useProjectFromDoc.ts | 4 +- .../src/composables/useServerDetector.ts | 4 +- .../src/composables/useViewVisibility.ts | 9 -- .../src/composables/useVscodeContext.ts | 15 ---- packages/vscode/src/configs.ts | 65 ++++---------- packages/vscode/src/index.ts | 49 ++++------ packages/vscode/src/projects.ts | 11 +-- .../vscode/src/utils/singletonComposable.ts | 15 ---- packages/vscode/src/utils/toRelativePath.ts | 3 +- packages/vscode/src/views/annotations.ts | 4 +- packages/vscode/src/views/foldings.ts | 7 +- packages/vscode/src/views/logger.ts | 32 +------ packages/vscode/src/views/previewWebview.ts | 71 +++++++-------- packages/vscode/src/views/projectsTree.ts | 89 ++++++++----------- packages/vscode/src/views/serverTerminal.ts | 16 ++++ packages/vscode/src/views/slidesTree.ts | 86 +++++++----------- packages/vscode/src/views/terminal.ts | 56 ------------ packages/vscode/tsconfig.json | 6 ++ packages/vscode/tsup.config.ts | 17 +--- pnpm-lock.yaml | 27 ++++-- taze.config.ts | 2 + 31 files changed, 254 insertions(+), 521 deletions(-) delete mode 100644 packages/vscode/src/composables/useActiveTextEditor.ts delete mode 100644 packages/vscode/src/composables/useDisposable.ts delete mode 100644 packages/vscode/src/composables/useDocumentText.ts delete mode 100644 packages/vscode/src/composables/useViewVisibility.ts delete mode 100644 packages/vscode/src/composables/useVscodeContext.ts delete mode 100644 packages/vscode/src/utils/singletonComposable.ts create mode 100644 packages/vscode/src/views/serverTerminal.ts delete mode 100644 packages/vscode/src/views/terminal.ts create mode 100644 packages/vscode/tsconfig.json diff --git a/packages/vscode/README.md b/packages/vscode/README.md index c0101b7b1d..551167a52e 100644 --- a/packages/vscode/README.md +++ b/packages/vscode/README.md @@ -13,6 +13,7 @@ Presentation slides for developers

Visual Studio Marketplace Version Visual Studio Marketplace Downloads +Made with reacrive-vscode


diff --git a/packages/vscode/package.json b/packages/vscode/package.json index e17308c8c1..43b21310df 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -349,8 +349,8 @@ "scripts": { "publish": "esno scripts/publish.ts", "pack": "vsce package --no-dependencies", - "build": "tsup --env.NODE_ENV production", - "dev": "nr build --watch", + "build": "tsup --env.NODE_ENV production --treeshake", + "dev": "tsup --watch ./src --env.NODE_ENV development", "vscode:prepublish": "nr build" }, "devDependencies": { @@ -359,10 +359,8 @@ "@slidev/types": "workspace:*", "@types/node": "^20.14.10", "@types/vscode": "~1.89.0", - "@vue/reactivity": "^3.4.31", - "@vue/runtime-core": "^3.4.31", - "@vue/shared": "^3.4.31", "get-port-please": "^3.1.2", - "ovsx": "^0.9.1" + "ovsx": "^0.9.1", + "reactive-vscode": "0.2.0-beta.3" } } diff --git a/packages/vscode/src/commands.ts b/packages/vscode/src/commands.ts index b21b0dfd13..f769aadea7 100644 --- a/packages/vscode/src/commands.ts +++ b/packages/vscode/src/commands.ts @@ -1,9 +1,8 @@ import { relative } from 'node:path' -import { onScopeDispose } from '@vue/runtime-core' -import type { Disposable } from 'vscode' -import { Position, Range, Selection, TextEditorRevealType, Uri, commands, window, workspace } from 'vscode' -import { save as saveSlidevMarkdown } from '@slidev/parser/fs' import { slash } from '@antfu/utils' +import { save as saveSlidevMarkdown } from '@slidev/parser/fs' +import { useCommand } from 'reactive-vscode' +import { Position, Range, Selection, TextEditorRevealType, Uri, window, workspace } from 'vscode' import { useDevServer } from './composables/useDevServer' import { useEditingSlideSource } from './composables/useEditingSlideSource' import { useFocusedSlideNo } from './composables/useFocusedSlideNo' @@ -12,21 +11,15 @@ import type { SlidevProject } from './projects' import { activeEntry, activeProject, activeSlidevData, addProject, projects, rescanProjects } from './projects' import { findPossibleEntries } from './utils/findPossibleEntries' import { usePreviewWebview } from './views/previewWebview' -import type { SlidesTreeElement } from './views/slidesTree' +import type { SlidesTreeNode } from './views/slidesTree' export function useCommands() { - const disposables: Disposable[] = [] - onScopeDispose(() => disposables.forEach(d => d.dispose())) - function registerCommand(command: string, callback: (...args: any[]) => any) { - disposables.push(commands.registerCommand(command, callback)) - } - - registerCommand('slidev.enable-extension', () => forceEnabled.value = true) - registerCommand('slidev.disable-extension', () => forceEnabled.value = false) + useCommand('slidev.enable-extension', () => forceEnabled.value = true) + useCommand('slidev.disable-extension', () => forceEnabled.value = false) - registerCommand('slidev.rescan-projects', rescanProjects) + useCommand('slidev.rescan-projects', rescanProjects) - registerCommand('slidev.choose-entry', async () => { + useCommand('slidev.choose-entry', async () => { const entry = await window.showQuickPick([...projects.keys()], { title: 'Choose active slides entry.', }) @@ -34,7 +27,7 @@ export function useCommands() { activeEntry.value = entry }) - registerCommand('slidev.add-entry', async () => { + useCommand('slidev.add-entry', async () => { const files = await findPossibleEntries() const selected = await window.showQuickPick(files, { title: 'Choose Markdown files to add as slides entries.', @@ -46,23 +39,24 @@ export function useCommands() { if (workspace.workspaceFolders) { const workspaceRoot = workspace.workspaceFolders[0].uri.fsPath const relatives = selected.map(s => slash(relative(workspaceRoot, s))) - include.value = [...include.value, ...relatives] + // write back to settings.json + include.update([...include.value, ...relatives]) } } }) - registerCommand('slidev.remove-entry', async (project: SlidevProject) => { + useCommand('slidev.remove-entry', async (project: SlidevProject) => { const entry = project.entry if (activeEntry.value === entry) activeEntry.value = null projects.delete(entry) }) - registerCommand('slidev.set-as-active', async (project: SlidevProject) => { + useCommand('slidev.set-as-active', async (project: SlidevProject) => { activeEntry.value = project.entry }) - registerCommand('slidev.stop-dev', async (project: SlidevProject) => { + useCommand('slidev.stop-dev', async (project: SlidevProject) => { const { stop } = useDevServer(project) stop() }) @@ -98,24 +92,24 @@ export function useCommands() { } } - registerCommand('slidev.goto', gotoSlide) - registerCommand('slidev.next', () => { + useCommand('slidev.goto', gotoSlide) + useCommand('slidev.next', () => { const { markdown, index } = useEditingSlideSource() const focusedSlideNo = useFocusedSlideNo() gotoSlide(markdown.value!.filepath, index.value + 1, () => focusedSlideNo.value + 1) }) - registerCommand('slidev.prev', () => { + useCommand('slidev.prev', () => { const { markdown, index } = useEditingSlideSource() const focusedSlideNo = useFocusedSlideNo() gotoSlide(markdown.value!.filepath, index.value - 1, () => focusedSlideNo.value - 1) }) - registerCommand('slidev.refresh-preview', () => { + useCommand('slidev.refresh-preview', () => { const { refresh } = usePreviewWebview() refresh() }) - registerCommand('slidev.config-port', async () => { + useCommand('slidev.config-port', async () => { const port = await window.showInputBox({ prompt: 'Slidev Preview Port', value: configuredPort.value.toString(), @@ -132,7 +126,7 @@ export function useCommands() { configuredPort.value = +port }) - registerCommand('slidev.start-dev', async () => { + useCommand('slidev.start-dev', async () => { const project = activeProject.value if (!project) { window.showErrorMessage('Cannot start dev server: No active slides project.') @@ -150,17 +144,17 @@ export function useCommands() { setTimeout(retry, 9000) }) - registerCommand('slidev.open-in-browser', () => usePreviewWebview().openExternal()) + useCommand('slidev.open-in-browser', () => usePreviewWebview().openExternal()) - registerCommand('slidev.preview-prev-click', () => usePreviewWebview().prevClick()) - registerCommand('slidev.preview-next-click', () => usePreviewWebview().nextClick()) - registerCommand('slidev.preview-prev-slide', () => usePreviewWebview().prevSlide()) - registerCommand('slidev.preview-next-slide', () => usePreviewWebview().nextSlide()) + useCommand('slidev.preview-prev-click', () => usePreviewWebview().prevClick()) + useCommand('slidev.preview-next-click', () => usePreviewWebview().nextClick()) + useCommand('slidev.preview-prev-slide', () => usePreviewWebview().prevSlide()) + useCommand('slidev.preview-next-slide', () => usePreviewWebview().nextSlide()) - registerCommand('slidev.enable-preview-sync', () => (previewSync.value = true)) - registerCommand('slidev.disable-preview-sync', () => (previewSync.value = false)) + useCommand('slidev.enable-preview-sync', () => (previewSync.value = true)) + useCommand('slidev.disable-preview-sync', () => (previewSync.value = false)) - registerCommand('slidev.remove-slide', async ({ slide }: SlidesTreeElement) => { + useCommand('slidev.remove-slide', async ({ slide }: SlidesTreeNode) => { const md = activeSlidevData.value!.markdownFiles[slide.filepath] md.slides.splice(md.slides.indexOf(slide), 1) await saveSlidevMarkdown(md) diff --git a/packages/vscode/src/composables/useActiveTextEditor.ts b/packages/vscode/src/composables/useActiveTextEditor.ts deleted file mode 100644 index 5039b1dfaf..0000000000 --- a/packages/vscode/src/composables/useActiveTextEditor.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { shallowRef } from '@vue/runtime-core' -import { window } from 'vscode' -import { createSingletonComposable } from '../utils/singletonComposable' -import { useDisposable } from './useDisposable' -import { useProjectFromDoc } from './useProjectFromDoc' -import { useVscodeContext } from './useVscodeContext' - -export const useActiveTextEditor = createSingletonComposable(() => { - const activeTextEditor = shallowRef(window.activeTextEditor) - - useDisposable(window.onDidChangeActiveTextEditor((editor) => { - activeTextEditor.value = editor - })) - - const projectInfo = useProjectFromDoc(() => activeTextEditor.value?.document) - useVscodeContext('slidev:editing-markdown', () => !!projectInfo.value) - - return activeTextEditor -}) diff --git a/packages/vscode/src/composables/useDevServer.ts b/packages/vscode/src/composables/useDevServer.ts index 17f54fcab2..35978747f4 100644 --- a/packages/vscode/src/composables/useDevServer.ts +++ b/packages/vscode/src/composables/useDevServer.ts @@ -1,17 +1,17 @@ import { basename } from 'node:path' -import type { Ref } from '@vue/runtime-core' -import { toRef } from '@vue/runtime-core' +import type { Ref } from 'reactive-vscode' +import { toRef } from 'reactive-vscode' import { getPort as getPortPlease } from 'get-port-please' import type { Terminal } from 'vscode' import type { SlidevProject } from '../projects' -import { useTerminal } from '../views/terminal' +import { useServerTerminal } from '../views/serverTerminal' import { useServerDetector } from './useServerDetector' export type Server = { port: Ref terminal: Ref - start: () => Promise - showTerminal: () => Promise + start: () => void + showTerminal: () => void stop: () => void } & ReturnType @@ -22,18 +22,18 @@ export function useDevServer(project: SlidevProject) { if (existing) return existing - const { terminal, isTerminalActive, showTerminal, sendText, closeTerminal } = useTerminal(project) + const { terminal, getIsActive, show: showTerminal, sendText, close } = useServerTerminal(project) const port = toRef(project, 'port') async function start() { - if (isTerminalActive()) + if (getIsActive()) return port.value ??= await getPort() sendText(`npm exec slidev -- --port ${port.value} ${JSON.stringify(basename(project.entry))}`) } function stop() { - closeTerminal() + close() port.value = null } diff --git a/packages/vscode/src/composables/useDisposable.ts b/packages/vscode/src/composables/useDisposable.ts deleted file mode 100644 index 2f322aa65f..0000000000 --- a/packages/vscode/src/composables/useDisposable.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { onScopeDispose } from '@vue/runtime-core' -import type { Disposable } from 'vscode' - -export function useDisposable(disposable: Disposable) { - onScopeDispose(() => disposable.dispose()) - return disposable -} diff --git a/packages/vscode/src/composables/useDocumentText.ts b/packages/vscode/src/composables/useDocumentText.ts deleted file mode 100644 index 9def73fcf8..0000000000 --- a/packages/vscode/src/composables/useDocumentText.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { MaybeRefOrGetter } from '@vue/runtime-core' -import { shallowRef, toValue, watchEffect } from '@vue/runtime-core' -import type { TextDocument } from 'vscode' -import { workspace } from 'vscode' -import { useDisposable } from './useDisposable' - -export function useDocumentText(doc: MaybeRefOrGetter) { - const text = shallowRef(toValue(doc)?.getText()) - - watchEffect(() => { - text.value = toValue(doc)?.getText() - }) - - useDisposable(workspace.onDidChangeTextDocument((ev) => { - if (ev.document === toValue(doc)) - text.value = ev.document.getText() - })) - - return text -} diff --git a/packages/vscode/src/composables/useEditingSlideSource.ts b/packages/vscode/src/composables/useEditingSlideSource.ts index 4fb4ccd2d7..26417fe880 100644 --- a/packages/vscode/src/composables/useEditingSlideSource.ts +++ b/packages/vscode/src/composables/useEditingSlideSource.ts @@ -1,33 +1,24 @@ -import { computed, ref, watch } from '@vue/runtime-core' -import { window } from 'vscode' -import { activeSlidevData } from '../projects' -import { createSingletonComposable } from '../utils/singletonComposable' -import { useActiveTextEditor } from './useActiveTextEditor' -import { useDisposable } from './useDisposable' +import { computed, createSingletonComposable, ref, useActiveTextEditor, useTextEditorSelection, watchEffect } from 'reactive-vscode' import { useProjectFromDoc } from './useProjectFromDoc' export const useEditingSlideSource = createSingletonComposable(() => { const editor = useActiveTextEditor() const projectInfo = useProjectFromDoc(() => editor.value?.document) const markdown = computed(() => projectInfo.value?.md) + const selection = useTextEditorSelection(editor) + const index = ref(0) - function updateSlideNo() { + watchEffect(() => { const md = markdown.value if (!md || !editor.value) { index.value = 0 return } - const line = editor.value.selection.active.line + 1 + const line = selection.value.active.line + 1 const slide = md.slides.find(s => line <= s.end) index.value = slide ? slide.index : md.slides.length - 1 - } - - updateSlideNo() - - useDisposable(window.onDidChangeTextEditorSelection(updateSlideNo)) - - watch([editor, activeSlidevData], updateSlideNo) + }) return { markdown, diff --git a/packages/vscode/src/composables/useFocusedSlideNo.ts b/packages/vscode/src/composables/useFocusedSlideNo.ts index f41e58e868..e61c4fe035 100644 --- a/packages/vscode/src/composables/useFocusedSlideNo.ts +++ b/packages/vscode/src/composables/useFocusedSlideNo.ts @@ -1,23 +1,21 @@ -import { ref, watch } from '@vue/runtime-core' -import { TextEditorSelectionChangeKind, window } from 'vscode' +import { createSingletonComposable, ref, useActiveTextEditor, useTextEditorSelection, watchEffect } from 'reactive-vscode' +import { TextEditorSelectionChangeKind } from 'vscode' import { activeSlidevData } from '../projects' import { getFirstDisplayedChild } from '../utils/getFirstDisplayedChild' -import { createSingletonComposable } from '../utils/singletonComposable' -import { useActiveTextEditor } from './useActiveTextEditor' -import { useDisposable } from './useDisposable' import { getProjectFromDoc } from './useProjectFromDoc' export const useFocusedSlideNo = createSingletonComposable(() => { const editor = useActiveTextEditor() + const selection = useTextEditorSelection(editor, [TextEditorSelectionChangeKind.Command, undefined]) const slideNo = ref(1) - function updateSlideNo() { + watchEffect(() => { const data = activeSlidevData.value const projectInfo = getProjectFromDoc(editor.value?.document) if (!data || !projectInfo || !editor.value) return - const line = editor.value.selection.active.line + 1 + const line = selection.value.active.line + 1 const slide = projectInfo.md.slides.find(s => s.start <= line && line <= s.end) if (slide) { const source = getFirstDisplayedChild(slide) @@ -28,16 +26,7 @@ export const useFocusedSlideNo = createSingletonComposable(() => { if (no) slideNo.value = no } - } - - updateSlideNo() - - useDisposable(window.onDidChangeTextEditorSelection(({ kind }) => { - if (kind !== TextEditorSelectionChangeKind.Command) - updateSlideNo() - })) - - watch(activeSlidevData, updateSlideNo) + }) return slideNo }) diff --git a/packages/vscode/src/composables/usePreviewState.ts b/packages/vscode/src/composables/usePreviewState.ts index 7150ed9102..d231be05ff 100644 --- a/packages/vscode/src/composables/usePreviewState.ts +++ b/packages/vscode/src/composables/usePreviewState.ts @@ -1,13 +1,11 @@ -import { computed, onScopeDispose, watchEffect } from '@vue/runtime-core' +import { computed, createSingletonComposable, onScopeDispose, useVscodeContext, watchEffect } from 'reactive-vscode' import { window } from 'vscode' -import { activeEntry, activeProject, projects } from '../projects' -import { createSingletonComposable } from '../utils/singletonComposable' import { configuredPort } from '../configs' -import { generateReadyHtml } from '../html/ready' import { generateErrorHtml } from '../html/error' +import { generateReadyHtml } from '../html/ready' +import { activeEntry, activeProject, projects } from '../projects' import { useDevServer } from './useDevServer' import { useServerDetector } from './useServerDetector' -import { useVscodeContext } from './useVscodeContext' export const usePreviewState = createSingletonComposable(() => { const detectServer = useServerDetector(configuredPort) diff --git a/packages/vscode/src/composables/useProjectFromDoc.ts b/packages/vscode/src/composables/useProjectFromDoc.ts index 9474180e4e..4a84b70bef 100644 --- a/packages/vscode/src/composables/useProjectFromDoc.ts +++ b/packages/vscode/src/composables/useProjectFromDoc.ts @@ -1,6 +1,6 @@ import { slash } from '@antfu/utils' -import type { MaybeRefOrGetter } from '@vue/runtime-core' -import { computed, toValue } from '@vue/runtime-core' +import type { MaybeRefOrGetter } from 'reactive-vscode' +import { computed, toValue } from 'reactive-vscode' import type { TextDocument } from 'vscode' import { activeProject, projects } from '../projects' diff --git a/packages/vscode/src/composables/useServerDetector.ts b/packages/vscode/src/composables/useServerDetector.ts index 24debb7dab..99d0e4aaf6 100644 --- a/packages/vscode/src/composables/useServerDetector.ts +++ b/packages/vscode/src/composables/useServerDetector.ts @@ -1,5 +1,5 @@ -import type { Ref } from '@vue/runtime-core' -import { reactive, watch } from '@vue/runtime-core' +import type { Ref } from 'reactive-vscode' +import { reactive, watch } from 'reactive-vscode' const versionRE = // const entryRE = // diff --git a/packages/vscode/src/composables/useViewVisibility.ts b/packages/vscode/src/composables/useViewVisibility.ts deleted file mode 100644 index 8636ef76cc..0000000000 --- a/packages/vscode/src/composables/useViewVisibility.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ref } from '@vue/runtime-core' -import type { TreeView } from 'vscode' -import { useDisposable } from './useDisposable' - -export function useViewVisibility(view: TreeView) { - const visible = ref(view.visible) - useDisposable(view.onDidChangeVisibility(ev => visible.value = ev.visible)) - return visible -} diff --git a/packages/vscode/src/composables/useVscodeContext.ts b/packages/vscode/src/composables/useVscodeContext.ts deleted file mode 100644 index fdf6a72de3..0000000000 --- a/packages/vscode/src/composables/useVscodeContext.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { ComputedRef, MaybeRefOrGetter } from '@vue/runtime-core' -import { computed, toValue, watchEffect } from '@vue/runtime-core' -import { commands } from 'vscode' - -export function useVscodeContext( - name: string, - value: MaybeRefOrGetter | ComputedRef, - shouldUpdate: MaybeRefOrGetter | ComputedRef = true, -) { - watchEffect(() => { - if (toValue(shouldUpdate)) - commands.executeCommand('setContext', name, toValue(value)) - }) - return computed(() => toValue(value)) -} diff --git a/packages/vscode/src/configs.ts b/packages/vscode/src/configs.ts index 05bc674b76..de4a32f5fe 100644 --- a/packages/vscode/src/configs.ts +++ b/packages/vscode/src/configs.ts @@ -1,49 +1,18 @@ -import { isDeepEqual } from '@antfu/utils' -import type { Ref, ShallowRef } from '@vue/runtime-core' -import { ref, shallowRef, watch } from '@vue/runtime-core' -import type { ColorTheme, ConfigurationChangeEvent } from 'vscode' -import { ColorThemeKind, window, workspace } from 'vscode' -import { useDisposable } from './composables/useDisposable' +import type { ConfigType } from 'reactive-vscode' +import { defineConfigs } from 'reactive-vscode' -const config = workspace.getConfiguration('slidev') -const configKeys: Record> = {} - -function useConfiguration(key: string, defaultValue: T, writeBack = false): Ref { - const r = configKeys[key] = shallowRef(config.get(key) ?? defaultValue) - if (writeBack) { - watch(r, (v) => { - if (!isDeepEqual(v, config.get(key))) - config.update(key, v) - }) - } - return r -} - -export const forceEnabled = useConfiguration('force-enabled', null, true) -export const configuredPort = useConfiguration('port', 3030) -export const displayAnnotations = useConfiguration('annotations', true) -export const previewSync = useConfiguration('preview-sync', true) -export const include = useConfiguration('include', ['**/slides.md'], true) -export const exclude = useConfiguration('exclude', '**/node_modules/**') - -export const isDarkTheme = ref(true) - -export function useGlobalConfigurations() { - function updateConfigurations(ev: ConfigurationChangeEvent) { - if (!ev.affectsConfiguration('slidev')) - return - const newConfig = workspace.getConfiguration('slidev') - for (const key in configKeys) { - if (ev.affectsConfiguration(`slidev.${key}`)) - configKeys[key].value = newConfig.get(key) - } - } - - useDisposable(workspace.onDidChangeConfiguration(updateConfigurations)) - - function updateIsDark(theme: ColorTheme) { - isDarkTheme.value = theme.kind === ColorThemeKind.Dark || theme.kind === ColorThemeKind.HighContrast - } - updateIsDark(window.activeColorTheme) - useDisposable(window.onDidChangeActiveColorTheme(updateIsDark)) -} +export const { + 'force-enabled': forceEnabled, + 'port': configuredPort, + 'annotations': displayAnnotations, + 'preview-sync': previewSync, + include, + exclude, +} = defineConfigs('slidev', { + 'force-enabled': Boolean, + 'port': Number, + 'annotations': Boolean, + 'preview-sync': Boolean, + 'include': Object as ConfigType, + 'exclude': String, +}) diff --git a/packages/vscode/src/index.ts b/packages/vscode/src/index.ts index 6d7858b2eb..40e0aba42f 100644 --- a/packages/vscode/src/index.ts +++ b/packages/vscode/src/index.ts @@ -1,43 +1,28 @@ -import { effectScope, shallowRef } from '@vue/runtime-core' -import type { ExtensionContext } from 'vscode' +import { defineExtension } from 'reactive-vscode' import { useCommands } from './commands' -import { useGlobalConfigurations } from './configs' import { activeEntry, useProjects } from './projects' import { useAnnotations } from './views/annotations' import { useFoldings } from './views/foldings' -import { useLogger } from './views/logger' +import { logger } from './views/logger' import { usePreviewWebview } from './views/previewWebview' import { useSlidesTree } from './views/slidesTree' import { useProjectsTree } from './views/projectsTree' -const scope = effectScope() +// eslint-disable-next-line no-restricted-syntax +export = defineExtension(() => { + // states + useProjects() -export const extCtx = shallowRef(undefined!) + // commands + useCommands() -export async function activate(ext: ExtensionContext) { - extCtx.value = ext + // views + useProjectsTree() + useSlidesTree() + usePreviewWebview() + useAnnotations() + useFoldings() - scope.run(() => { - // states - useGlobalConfigurations() - useProjects() - - // commands - useCommands() - - // views - useProjectsTree() - useSlidesTree() - usePreviewWebview() - useAnnotations() - useFoldings() - - const logger = useLogger() - logger.info('Slidev activated.') - logger.info(`Entry: ${activeEntry.value}`) - }) -} - -export async function deactivate() { - scope.stop() -} + logger.info('Slidev activated.') + logger.info(`Entry: ${activeEntry.value}`) +}) diff --git a/packages/vscode/src/projects.ts b/packages/vscode/src/projects.ts index 3637fff293..d4170c5eb9 100644 --- a/packages/vscode/src/projects.ts +++ b/packages/vscode/src/projects.ts @@ -1,14 +1,13 @@ import { existsSync } from 'node:fs' import { basename, dirname } from 'node:path' +import { slash } from '@antfu/utils' import type { LoadedSlidevData } from '@slidev/parser/fs' import { load } from '@slidev/parser/fs' -import { computed, markRaw, onScopeDispose, reactive, ref, watch, watchEffect } from '@vue/runtime-core' +import { computed, markRaw, onScopeDispose, reactive, ref, useVscodeContext, watch, watchEffect } from 'reactive-vscode' import { window, workspace } from 'vscode' -import { slash } from '@antfu/utils' -import { useLogger } from './views/logger' -import { findShallowestPath } from './utils/findShallowestPath' -import { useVscodeContext } from './composables/useVscodeContext' import { exclude, forceEnabled, include } from './configs' +import { findShallowestPath } from './utils/findShallowestPath' +import { logger } from './views/logger' export interface SlidevProject { readonly entry: string @@ -49,8 +48,6 @@ export async function rescanProjects() { } export function useProjects() { - const logger = useLogger() - async function init() { await addExistingProjects() await autoSetActiveEntry() diff --git a/packages/vscode/src/utils/singletonComposable.ts b/packages/vscode/src/utils/singletonComposable.ts deleted file mode 100644 index e63c2fa5b3..0000000000 --- a/packages/vscode/src/utils/singletonComposable.ts +++ /dev/null @@ -1,15 +0,0 @@ -import process from 'node:process' - -export function createSingletonComposable(fn: () => T): () => T { - let result: T | undefined - return () => { - if (!result) { - if (process.env.NODE_ENV !== 'production') { - // @ts-expect-error Debug only check - result = 'Cannot call singleton composable recursively!' - } - result = fn() - } - return result - } -} diff --git a/packages/vscode/src/utils/toRelativePath.ts b/packages/vscode/src/utils/toRelativePath.ts index 60d1edf727..a1911ab834 100644 --- a/packages/vscode/src/utils/toRelativePath.ts +++ b/packages/vscode/src/utils/toRelativePath.ts @@ -2,8 +2,7 @@ import { relative } from 'node:path' import { slash } from '@antfu/utils' import { workspace } from 'vscode' -const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath ?? '' - export function toRelativePath(path: string) { + const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath ?? '' return slash(relative(workspaceRoot, path)) } diff --git a/packages/vscode/src/views/annotations.ts b/packages/vscode/src/views/annotations.ts index f7dddd0d83..978d39347b 100644 --- a/packages/vscode/src/views/annotations.ts +++ b/packages/vscode/src/views/annotations.ts @@ -1,13 +1,11 @@ import { clamp, ensurePrefix } from '@antfu/utils' import type { SourceSlideInfo } from '@slidev/types' -import { computed, watch } from '@vue/runtime-core' +import { computed, createSingletonComposable, useActiveTextEditor, watch } from 'reactive-vscode' import type { DecorationOptions } from 'vscode' import { Position, Range, ThemeColor, window } from 'vscode' -import { useActiveTextEditor } from '../composables/useActiveTextEditor' import { useProjectFromDoc } from '../composables/useProjectFromDoc' import { displayAnnotations } from '../configs' import { activeProject } from '../projects' -import { createSingletonComposable } from '../utils/singletonComposable' import { toRelativePath } from '../utils/toRelativePath' const dividerCommonOptions = { diff --git a/packages/vscode/src/views/foldings.ts b/packages/vscode/src/views/foldings.ts index dcb4815b76..47cb7c411f 100644 --- a/packages/vscode/src/views/foldings.ts +++ b/packages/vscode/src/views/foldings.ts @@ -1,17 +1,20 @@ import { parse } from '@slidev/parser' import type { TextDocument } from 'vscode' import { FoldingRangeKind, languages } from 'vscode' -import { useDisposable } from '../composables/useDisposable' +import { createSingletonComposable, useDisposable, useEventEmitter, watch } from 'reactive-vscode' import { getProjectFromDoc } from '../composables/useProjectFromDoc' -import { createSingletonComposable } from '../utils/singletonComposable' +import { projects } from '../projects' export const useFoldings = createSingletonComposable(() => { + const emitter = useEventEmitter() + watch(projects, () => emitter.fire(), { deep: true }) useDisposable(languages.registerFoldingRangeProvider( { scheme: 'file', language: 'markdown', }, { + onDidChangeFoldingRanges: emitter.event, async provideFoldingRanges(document: TextDocument) { if (!getProjectFromDoc(document)) return // Not a slidev markdown file diff --git a/packages/vscode/src/views/logger.ts b/packages/vscode/src/views/logger.ts index 5e0e60d968..4c4afd07ad 100644 --- a/packages/vscode/src/views/logger.ts +++ b/packages/vscode/src/views/logger.ts @@ -1,31 +1,3 @@ -import { onScopeDispose } from '@vue/runtime-core' -import { window } from 'vscode' -import { createSingletonComposable } from '../utils/singletonComposable' +import { useLogger } from 'reactive-vscode' -export const useLogger = createSingletonComposable(() => { - const outputChannel = window.createOutputChannel('Slidev') - - onScopeDispose(() => outputChannel.dispose()) - - const createLogger = (type: string) => (message: string) => { - const date = new Date() - const year = String(date.getFullYear()).padStart(4, '0') - const month = String(date.getMonth() + 1).padStart(2, '0') - const day = String(date.getDate()).padStart(2, '0') - const hour = String(date.getHours()).padStart(2, '0') - const minute = String(date.getMinutes()).padStart(2, '0') - const second = String(date.getSeconds()).padStart(2, '0') - const millisecond = String(date.getMilliseconds()).padStart(3, '0') - outputChannel.appendLine( - `${year}-${month}-${day} ${hour}:${minute}:${second}.${millisecond} [${type}] ${message}`, - ) - } - - return { - outputChannel, - info: createLogger('INFO'), - warn: createLogger('WARN'), - error: createLogger('ERROR'), - appendLine: (message: string) => outputChannel.appendLine(message), - } -}) +export const logger = useLogger('Slidev') diff --git a/packages/vscode/src/views/previewWebview.ts b/packages/vscode/src/views/previewWebview.ts index 4c9fc3fa6c..b66f38538a 100644 --- a/packages/vscode/src/views/previewWebview.ts +++ b/packages/vscode/src/views/previewWebview.ts @@ -1,24 +1,18 @@ -import { reactive, ref, shallowRef, watch, watchEffect } from '@vue/runtime-core' -import type { WebviewView } from 'vscode' +import { createSingletonComposable, extensionContext, reactive, ref, useIsDarkTheme, useVscodeContext, useWebviewView, watch, watchEffect } from 'reactive-vscode' import { Uri, commands, env, window } from 'vscode' -import { useDisposable } from '../composables/useDisposable' import { useFocusedSlideNo } from '../composables/useFocusedSlideNo' import { usePreviewState } from '../composables/usePreviewState' -import { useVscodeContext } from '../composables/useVscodeContext' -import { isDarkTheme, previewSync } from '../configs' -import { extCtx } from '../index' +import { previewSync } from '../configs' import { activeSlidevData } from '../projects' -import { createSingletonComposable } from '../utils/singletonComposable' -import { useLogger } from './logger' +import { logger } from './logger' export const usePreviewWebview = createSingletonComposable(() => { - const logger = useLogger() const { port, ready, html, compatMode, refreshState } = usePreviewState() const focusedSlideNo = useFocusedSlideNo() + const isDarkTheme = useIsDarkTheme() useVscodeContext('slidev:preview:sync', previewSync) - const view = shallowRef() const previewNavState = reactive({ no: 0, clicks: 0, @@ -27,49 +21,46 @@ export const usePreviewWebview = createSingletonComposable(() => { }) const initializedClientId = ref('') - useDisposable(window.registerWebviewViewProvider( + const { view, postMessage, forceRefresh } = useWebviewView( 'slidev-preview', + html, { - resolveWebviewView(webviewView) { - view.value = webviewView - view.value.webview.options = { - enableScripts: true, - localResourceRoots: [extCtx.value.extensionUri], + retainContextWhenHidden: true, + webviewOptions: { + enableScripts: true, + localResourceRoots: [extensionContext.value!.extensionUri], + }, + async onDidReceiveMessage(data) { + if (data.type === 'command') { + commands.executeCommand(`slidev.${data.command}`) } - view.value.webview.onDidReceiveMessage(async (data) => { - if (data.type === 'command') { - commands.executeCommand(`slidev.${data.command}`) + else if (data.type === 'update-state') { + if (initializedClientId.value === data.clientId) { + Object.assign(previewNavState, data.navState) } - else if (data.type === 'update-state') { - if (initializedClientId.value === data.clientId) { - Object.assign(previewNavState, data.navState) - } - else { - initializedClientId.value = data.clientId - if (previewSync.value && initializedClientId.value === data.clientId) - postMessage('navigate', { no: focusedSlideNo.value }) - } + else { + initializedClientId.value = data.clientId + if (previewSync.value && initializedClientId.value === data.clientId) + postSlidevMessage('navigate', { no: focusedSlideNo.value }) } - }) + } }, }, - { webviewOptions: { retainContextWhenHidden: true } }, - )) + ) const pageId = ref(0) - let i = 0 function refresh() { refreshState() if (!view.value) return - view.value.webview.html = `${html.value}` + forceRefresh() logger.info(`Webview refreshed. Current URL: http://localhost:${port.value}`) setTimeout(() => pageId.value++, 300) } - watchEffect(refresh) + watch([view, port], refresh) - function postMessage(type: string, data: Record) { - view.value?.webview.postMessage({ + function postSlidevMessage(type: string, data: Record) { + postMessage({ target: 'slidev', sender: 'vscode', type, @@ -81,11 +72,11 @@ export const usePreviewWebview = createSingletonComposable(() => { if (sync) { if (compatMode.value) previewNavState.no = no - postMessage('navigate', { no, clicks: 999999 }) + postSlidevMessage('navigate', { no, clicks: 999999 }) } }) - watch([pageId], () => postMessage('css-vars', { '--slidev-slide-container-background': 'transparent' })) - watch([pageId, isDarkTheme], ([_, dark]) => postMessage('color-schema', { color: dark ? 'dark' : 'light' })) + watch([pageId], () => postSlidevMessage('css-vars', { '--slidev-slide-container-background': 'transparent' })) + watch([pageId, isDarkTheme], ([_, dark]) => postSlidevMessage('color-schema', { color: dark ? 'dark' : 'light' })) watchEffect(() => { if (view.value) { @@ -99,7 +90,7 @@ export const usePreviewWebview = createSingletonComposable(() => { return () => { if (compatMode.value) window.showErrorMessage('Unsupported operation: Slidev server too old.') - postMessage('navigate', { operation, args }) + postSlidevMessage('navigate', { operation, args }) } } diff --git a/packages/vscode/src/views/projectsTree.ts b/packages/vscode/src/views/projectsTree.ts index 208e716a9b..e67b86a367 100644 --- a/packages/vscode/src/views/projectsTree.ts +++ b/packages/vscode/src/views/projectsTree.ts @@ -1,11 +1,10 @@ +import { computed, createSingletonComposable, useTreeView } from 'reactive-vscode' import type { TreeItem } from 'vscode' -import { EventEmitter, ThemeIcon, TreeItemCollapsibleState, Uri, window } from 'vscode' -import { computed, onScopeDispose, watch } from '@vue/runtime-core' -import { createSingletonComposable } from '../utils/singletonComposable' +import { ThemeIcon, TreeItemCollapsibleState, Uri } from 'vscode' import type { SlidevProject } from '../projects' import { activeEntry, projects } from '../projects' -import { toRelativePath } from '../utils/toRelativePath' import { getSlidesTitle } from '../utils/getSlidesTitle' +import { toRelativePath } from '../utils/toRelativePath' /** * No projects has dependency files. Then it would be weired to show a collapse button. @@ -13,60 +12,50 @@ import { getSlidesTitle } from '../utils/getSlidesTitle' const nothingToCollapse = computed(() => [...projects.values()] .every(project => Object.keys(project.data.markdownFiles).length <= 1)) -function getTreeItem(element: SlidevProject | string): TreeItem { - if (typeof element === 'string') { - // Imported file - return { - description: toRelativePath(element), - resourceUri: Uri.file(element), - iconPath: new ThemeIcon('file'), - collapsibleState: TreeItemCollapsibleState.None, - command: { - title: 'Open', - command: 'vscode.open', - arguments: [Uri.file(element)], - }, - } +function getProjectTreeItem(project: SlidevProject): TreeItem { + const active = activeEntry.value === project.entry + return { + label: getSlidesTitle(project.data), + description: `${toRelativePath(project.entry)}${project.port ? ` (port: ${project.port})` : ''}`, + resourceUri: Uri.file(project.entry), + iconPath: new ThemeIcon(active ? 'eye' : 'eye-closed'), + collapsibleState: nothingToCollapse.value ? TreeItemCollapsibleState.None : active ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed, + contextValue: `<${active ? 'active' : 'inactive'}><${project.port ? 'up' : 'down'}>`, + command: { + title: 'Open', + command: 'vscode.open', + arguments: [Uri.file(project.entry)], + }, } - else { - // Slides project - const active = activeEntry.value === element.entry - return { - label: getSlidesTitle(element.data), - description: `${toRelativePath(element.entry)}${element.port ? ` (port: ${element.port})` : ''}`, - resourceUri: Uri.file(element.entry), - iconPath: new ThemeIcon(active ? 'eye' : 'eye-closed'), - collapsibleState: nothingToCollapse.value ? TreeItemCollapsibleState.None : active ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed, - contextValue: `<${active ? 'active' : 'inactive'}><${element.port ? 'up' : 'down'}>`, - command: { - title: 'Open', - command: 'vscode.open', - arguments: [Uri.file(element.entry)], - }, - } +} + +function getFileTreeItem(file: string): TreeItem { + return { + description: toRelativePath(file), + resourceUri: Uri.file(file), + iconPath: new ThemeIcon('file'), + collapsibleState: TreeItemCollapsibleState.None, + command: { + title: 'Open', + command: 'vscode.open', + arguments: [Uri.file(file)], + }, } } export const useProjectsTree = createSingletonComposable(() => { - const onChange = new EventEmitter() + const treeData = computed(() => { + return [...projects.values()].map(project => ({ + treeItem: getProjectTreeItem(project), + children: project.data.watchFiles + .filter(file => file.toLowerCase() !== project.entry.toLowerCase()) + .map(file => ({ treeItem: getFileTreeItem(file) })), + })) + }) - const treeView = window.createTreeView('slidev-projects-tree', { - treeDataProvider: { - onDidChangeTreeData: onChange.event, - getTreeItem, - getChildren(element) { - return element - ? typeof element === 'string' - ? undefined - : element.data.watchFiles.filter(file => file.toLowerCase() !== element.entry.toLowerCase()) - : [...projects.values()] - }, - }, + const treeView = useTreeView('slidev-projects-tree', treeData, { showCollapseAll: true, }) - onScopeDispose(() => treeView.dispose()) - - watch([projects, activeEntry], () => onChange.fire(), { deep: true }) return treeView }) diff --git a/packages/vscode/src/views/serverTerminal.ts b/packages/vscode/src/views/serverTerminal.ts new file mode 100644 index 0000000000..109c4bd5d4 --- /dev/null +++ b/packages/vscode/src/views/serverTerminal.ts @@ -0,0 +1,16 @@ +import { useAbsolutePath, useControlledTerminal } from 'reactive-vscode' +import { Uri } from 'vscode' +import type { SlidevProject } from '../projects' +import { getSlidesTitle } from '../utils/getSlidesTitle' + +export function useServerTerminal(project: SlidevProject) { + return useControlledTerminal({ + name: getSlidesTitle(project.data), + cwd: project.userRoot, + iconPath: { + light: Uri.file(useAbsolutePath('dist/res/logo-mono.svg').value), + dark: Uri.file(useAbsolutePath('dist/res/logo-mono-dark.svg').value), + }, + isTransient: true, + }) +} diff --git a/packages/vscode/src/views/slidesTree.ts b/packages/vscode/src/views/slidesTree.ts index d818f77723..574ab860e9 100644 --- a/packages/vscode/src/views/slidesTree.ts +++ b/packages/vscode/src/views/slidesTree.ts @@ -1,14 +1,13 @@ import { save as slidevSave } from '@slidev/parser/fs' import type { SourceSlideInfo } from '@slidev/types' -import { computed, markRaw, onScopeDispose, watch, watchEffect } from '@vue/runtime-core' +import type { TreeViewNode } from 'reactive-vscode' +import { computed, createSingletonComposable, useTreeView, useViewVisibility, watch } from 'reactive-vscode' import type { TreeItem } from 'vscode' -import { DataTransferItem, EventEmitter, ThemeIcon, TreeItemCollapsibleState, commands, window } from 'vscode' -import { useViewVisibility } from '../composables/useViewVisibility' +import { DataTransferItem, ThemeIcon, TreeItemCollapsibleState, commands, window } from 'vscode' import { previewSync } from '../configs' import { activeSlidevData } from '../projects' import { getSlideNo } from '../utils/getSlideNo' import { getSlidesTitle } from '../utils/getSlidesTitle' -import { createSingletonComposable } from '../utils/singletonComposable' import { toRelativePath } from '../utils/toRelativePath' import { usePreviewWebview } from './previewWebview' @@ -36,15 +35,14 @@ const layoutIconMap = { 'two-cols': 'split-horizontal', } as Record -export interface SlidesTreeElement { - parent: SlidesTreeElement | null - children?: SlidesTreeElement[] +export interface SlidesTreeNode { + parent: SlidesTreeNode | null slide: SourceSlideInfo } -function getImportChain(element: SlidesTreeElement): SourceSlideInfo[] { +function getImportChain(node: SlidesTreeNode): SourceSlideInfo[] { const chain: SourceSlideInfo[] = [] - let parent = element.parent + let parent = node.parent while (parent) { chain.unshift(parent.slide) parent = parent.parent @@ -52,17 +50,17 @@ function getImportChain(element: SlidesTreeElement): SourceSlideInfo[] { return chain } -function getGotoCommandArgs(element: SlidesTreeElement) { - const slide = element.slide +function getGotoCommandArgs(node: SlidesTreeNode) { + const slide = node.slide return [ slide.filepath, slide.index, - () => getSlideNo(activeSlidevData.value, slide, getImportChain(element)), + () => getSlideNo(activeSlidevData.value, slide, getImportChain(node)), ] } -function getTreeItem(element: SlidesTreeElement): TreeItem { - const slide = element.slide +function getTreeItem(node: SlidesTreeNode): TreeItem { + const slide = node.slide const isFirstSlide = activeSlidevData.value?.entry.slides.findIndex(s => s === slide) === 0 const layoutName = slide.frontmatter.layout || (isFirstSlide ? 'cover' : 'default') const icon = slide.imports ? 'link-external' : layoutIconMap[layoutName] ?? 'window' @@ -73,48 +71,37 @@ function getTreeItem(element: SlidesTreeElement): TreeItem { command: { command: 'slidev.goto', title: 'Goto', - arguments: getGotoCommandArgs(element), + arguments: getGotoCommandArgs(node), }, collapsibleState: slide.imports ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None, } } export const useSlidesTree = createSingletonComposable(() => { - const onChange = new EventEmitter() - const slidesTreeData = computed(() => { - function createElement(parent: SlidesTreeElement | null, slide: SourceSlideInfo) { - const element: SlidesTreeElement = markRaw({ parent, slide }) - element.children = slide.imports?.map(s => createElement(element, s)) - return element + function createNode(parent: SlidesTreeNode | null, slide: SourceSlideInfo): TreeViewNode & SlidesTreeNode { + const node: SlidesTreeNode = { parent, slide } + return { + ...node, + children: slide.imports?.map(s => createNode(node, s)), + treeItem: getTreeItem(node), + } } - return activeSlidevData.value?.entry.slides.map(s => createElement(null, s)) + return activeSlidevData.value?.entry.slides.map(s => createNode(null, s)) ?? [] }) - const treeView = window.createTreeView('slidev-slides-tree', { - treeDataProvider: { - onDidChangeTreeData: onChange.event, - getTreeItem, - getChildren(element) { - return element - ? element.children - : slidesTreeData.value - }, - getParent(element) { - return element.parent - }, - }, + const treeView = useTreeView('slidev-slides-tree', slidesTreeData, { canSelectMany: true, dragAndDropController: { dragMimeTypes: [slideMineType], dropMimeTypes: [slideMineType], - handleDrag(elements, dataTransfer) { + handleDrag(source, dataTransfer) { const data = activeSlidevData.value if (!data) { window.showErrorMessage(`Cannot drag and drop slides: No active slides project.`) return } - const sourcesInEntry = elements.map(element => element.slide).filter(s => s.filepath === data.entry.filepath) + const sourcesInEntry = source.map(node => node.slide).filter(s => s.filepath === data.entry.filepath) dataTransfer.set(slideMineType, new DataTransferItem(sourcesInEntry)) }, async handleDrop(target, dataTransfer) { @@ -137,10 +124,10 @@ export const useSlidesTree = createSingletonComposable(() => { }, }, showCollapseAll: true, + title: () => activeSlidevData.value + ? `Slides (${getSlidesTitle(activeSlidevData.value)})` + : 'Slides', }) - onScopeDispose(() => treeView.dispose()) - - watch(activeSlidevData, () => onChange.fire()) const visible = useViewVisibility(treeView) const { previewNavState } = usePreviewWebview() @@ -154,29 +141,22 @@ export const useSlidesTree = createSingletonComposable(() => { return const path = (slide.importChain ?? []).concat(slide.source) const source = path.shift() - let element = tree?.find(e => e.slide === source) + let node = tree?.find(e => e.slide === source) while (true) { const source = path.shift() if (!source) { - if (element) { - treeView.reveal(element, { select: true }) - commands.executeCommand('slidev.goto', ...getGotoCommandArgs(element)) + if (node) { + treeView.reveal(node, { select: true }) + commands.executeCommand('slidev.goto', ...getGotoCommandArgs(node)) } return } - element = element?.children?.find(e => e.slide === source) - if (!element) + node = node?.children?.find(e => e.slide === source) + if (!node) return } }, ) - watchEffect(() => { - if (activeSlidevData.value) - treeView.title = `Slides (${getSlidesTitle(activeSlidevData.value)})` - else - treeView.title = 'Slides' - }) - return treeView }) diff --git a/packages/vscode/src/views/terminal.ts b/packages/vscode/src/views/terminal.ts deleted file mode 100644 index a11b38bdef..0000000000 --- a/packages/vscode/src/views/terminal.ts +++ /dev/null @@ -1,56 +0,0 @@ -// Ported from https://github.com/antfu/vscode-vite/blob/main/src/terminal.ts - -import { ref } from '@vue/runtime-core' -import type { Terminal } from 'vscode' -import { Uri, window } from 'vscode' -import { extCtx } from '../index' -import type { SlidevProject } from '../projects' -import { getSlidesTitle } from '../utils/getSlidesTitle' - -export function useTerminal(project: SlidevProject) { - const terminal = ref(null) - - function isTerminalActive() { - return terminal.value && terminal.value.exitStatus == null - } - - function ensureTerminal() { - if (isTerminalActive()) - return - terminal.value = window.createTerminal({ - name: getSlidesTitle(project.data), - cwd: project.userRoot, - iconPath: { - light: Uri.file(extCtx.value.asAbsolutePath('dist/res/logo-mono.svg')), - dark: Uri.file(extCtx.value.asAbsolutePath('dist/res/logo-mono-dark.svg')), - }, - isTransient: true, - }) - } - - async function sendText(text: string) { - ensureTerminal() - terminal.value!.sendText(text) - } - - async function showTerminal() { - ensureTerminal() - terminal.value!.show() - } - - function closeTerminal() { - if (isTerminalActive()) { - terminal.value!.sendText('\x03') - terminal.value!.dispose() - terminal.value = null - } - } - - return { - terminal, - isTerminalActive, - showTerminal, - sendText, - closeTerminal, - } -} diff --git a/packages/vscode/tsconfig.json b/packages/vscode/tsconfig.json new file mode 100644 index 0000000000..ce2b17ef8d --- /dev/null +++ b/packages/vscode/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "Preserve" + } +} diff --git a/packages/vscode/tsup.config.ts b/packages/vscode/tsup.config.ts index bbf4f4a722..2b90e8e064 100644 --- a/packages/vscode/tsup.config.ts +++ b/packages/vscode/tsup.config.ts @@ -1,6 +1,5 @@ import { copyFileSync, existsSync, mkdirSync } from 'node:fs' import { join, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' import { defineConfig } from 'tsup' export default defineConfig({ @@ -12,24 +11,12 @@ export default defineConfig({ external: [ 'vscode', ], - plugins: [ - { - name: 'alias', - esbuildOptions(options) { - options.alias ||= {} - options.alias['@vue/runtime-core'] = fileURLToPath(new URL('../../node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js', import.meta.url)) - options.alias['@vue/reactivity'] = fileURLToPath(new URL('../../node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js', import.meta.url)) - options.alias['@vue/shared'] = fileURLToPath(new URL('../../node_modules/@vue/shared/dist/shared.esm-bundler.js', import.meta.url)) - }, - }, - ], async onSuccess() { const assetsDir = join(__dirname, '../../assets') const resDir = join(__dirname, './dist/res') - const iconsDir = join(resDir, 'icons') - if (!existsSync(iconsDir)) - mkdirSync(iconsDir, { recursive: true }) + if (!existsSync(resDir)) + mkdirSync(resDir, { recursive: true }) for (const file of ['logo-mono.svg', 'logo-mono-dark.svg', 'logo.png', 'logo.svg']) copyFileSync(resolve(assetsDir, file), resolve(resDir, file)) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 259347fd62..b60655e719 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -727,21 +727,15 @@ importers: '@types/vscode': specifier: ~1.89.0 version: 1.89.0 - '@vue/reactivity': - specifier: ^3.4.31 - version: 3.4.31 - '@vue/runtime-core': - specifier: ^3.4.31 - version: 3.4.31 - '@vue/shared': - specifier: ^3.4.31 - version: 3.4.31 get-port-please: specifier: ^3.1.2 version: 3.1.2 ovsx: specifier: ^0.9.1 version: 0.9.1 + reactive-vscode: + specifier: 0.2.0-beta.3 + version: 0.2.0-beta.3(@types/vscode@1.89.0) packages: @@ -1595,6 +1589,9 @@ packages: '@polka/url@1.0.0-next.24': resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==} + '@reactive-vscode/reactivity@0.2.0-beta.3': + resolution: {integrity: sha512-ggZqWGEv3suZrpXxuGoi49IjU8mMtZ7uk7TpchqUFFpy5mHQimNRDwgifgEx/f8VWcTJJE0rgYCz4Ps9fwk2fg==} + '@rollup/pluginutils@4.2.1': resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -5246,6 +5243,11 @@ packages: react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + reactive-vscode@0.2.0-beta.3: + resolution: {integrity: sha512-yhKif5ggsHnPYGPIQNN0/PwRytDKwra2pQOVF+bVqXSTZ1fXAM6x1E+vHXu5jyZ5sRcvOIbNvrb2uu+/ny45fw==} + peerDependencies: + '@types/vscode': ^1.89.0 + read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -7285,6 +7287,8 @@ snapshots: '@polka/url@1.0.0-next.24': {} + '@reactive-vscode/reactivity@0.2.0-beta.3': {} + '@rollup/pluginutils@4.2.1': dependencies: estree-walker: 2.0.2 @@ -11670,6 +11674,11 @@ snapshots: react-is@18.2.0: {} + reactive-vscode@0.2.0-beta.3(@types/vscode@1.89.0): + dependencies: + '@reactive-vscode/reactivity': 0.2.0-beta.3 + '@types/vscode': 1.89.0 + read-pkg-up@7.0.1: dependencies: find-up: 4.1.0 diff --git a/taze.config.ts b/taze.config.ts index 134f6fa972..2d15314bb8 100644 --- a/taze.config.ts +++ b/taze.config.ts @@ -8,5 +8,7 @@ export default defineConfig({ 'typeit': 'ignore', // `engines.vscode` must be updated when bumping `@types/vscode` version '@types/vscode': 'ignore', + // reactive-vscode is not stable yet + 'reactive-vscode': 'ignore', }, })