Skip to content

Commit 638e264

Browse files
kermanxCopilotautofix-ci[bot]
authored
feat: rework vscode extension (#2390)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 9992aff commit 638e264

30 files changed

Lines changed: 810 additions & 652 deletions

packages/client/composables/useEmbeddedCtrl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function useEmbeddedControl() {
4848
)
4949
},
5050
{
51-
throttle: 300,
51+
throttle: 50,
5252
immediate: true,
5353
},
5454
)

packages/parser/src/fs.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { PreparserExtensionLoader, SlideInfo, SlidevData, SlidevMarkdown, SlidevPreparserExtension, SourceSlideInfo } from '@slidev/types'
2-
import fs from 'node:fs'
2+
import { existsSync } from 'node:fs'
3+
import { readFile, writeFile } from 'node:fs/promises'
34
import { dirname, resolve } from 'node:path'
45
import { slash } from '@antfu/utils'
56
import YAML from 'yaml'
@@ -19,8 +20,15 @@ export function injectPreparserExtensionLoader(fn: PreparserExtensionLoader) {
1920
*/
2021
export type LoadedSlidevData = Omit<SlidevData, 'config' | 'themeMeta'>
2122

22-
export async function load(userRoot: string, filepath: string, loadedSource: Record<string, string> = {}, mode?: string): Promise<LoadedSlidevData> {
23-
const markdown = loadedSource[filepath] ?? fs.readFileSync(filepath, 'utf-8')
23+
export async function load(
24+
userRoot: string,
25+
filepath: string,
26+
sources: Record<string, string> | ((path: string) => Promise<string>) = {},
27+
mode?: string,
28+
): Promise<LoadedSlidevData> {
29+
const loadSource = typeof sources === 'function' ? sources : async (path: string) => sources[path] ?? readFile(path, 'utf-8')
30+
31+
const markdown = await loadSource(filepath)
2432

2533
let extensions: SlidevPreparserExtension[] | undefined
2634
if (preparserExtensionLoader) {
@@ -46,7 +54,7 @@ export async function load(userRoot: string, filepath: string, loadedSource: Rec
4654
async function loadMarkdown(path: string, range?: string, frontmatterOverride?: Record<string, unknown>, importers?: SourceSlideInfo[]) {
4755
let md = markdownFiles[path]
4856
if (!md) {
49-
const raw = loadedSource[path] ?? fs.readFileSync(path, 'utf-8')
57+
const raw = await loadSource(path)
5058
md = await parse(raw, path, extensions)
5159
markdownFiles[path] = md
5260
watchFiles[path] = new Set()
@@ -90,7 +98,7 @@ export async function load(userRoot: string, filepath: string, loadedSource: Rec
9098
}
9199
delete frontmatterOverride.src
92100

93-
if (!fs.existsSync(path)) {
101+
if (!existsSync(path)) {
94102
md.errors ??= []
95103
md.errors.push({
96104
row: slide.start,
@@ -135,6 +143,6 @@ export async function load(userRoot: string, filepath: string, loadedSource: Rec
135143

136144
export async function save(markdown: SlidevMarkdown) {
137145
const fileContent = stringify(markdown)
138-
fs.writeFileSync(markdown.filepath, fileContent, 'utf-8')
146+
await writeFile(markdown.filepath, fileContent, 'utf-8')
139147
return fileContent
140148
}

packages/types/src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ export interface SourceSlideInfo extends SlideInfoBase {
3636
*/
3737
contentRaw: string
3838
/**
39-
* Slides import by this slide.
39+
* Slides imported by this slide.
4040
*/
4141
imports?: SourceSlideInfo[]
42-
frontmatterDoc?: YAML.Document
42+
frontmatterDoc?: YAML.Document<YAML.Node, true>
4343
frontmatterStyle?: FrontmatterStyle
4444
}
4545

packages/vscode/language-server/volar-service-yaml.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ export function create({
8282
sendError: noop,
8383
sendTrack: noop,
8484
},
85-
// @ts-expect-error https://github.com/redhat-developer/yaml-language-server/pull/910
8685
clientCapabilities: context.env?.clientCapabilities,
8786
workspaceContext: getWorkspaceContextService(context),
8887
})

packages/vscode/package.json

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
"vscode": "^1.99.0"
2222
},
2323
"activationEvents": [
24-
"onStartupFinished"
24+
"workspaceContains:**/*.md"
2525
],
2626
"contributes": {
2727
"languages": [
2828
{
2929
"id": "markdown",
30-
"configuration": "./language/language-configuration.json"
30+
"configuration": "./syntaxes/language-configuration.json"
3131
},
3232
{
3333
"id": "slidev"
@@ -145,7 +145,7 @@
145145
{
146146
"command": "slidev.add-entry",
147147
"category": "Slidev",
148-
"title": "Choose Markdown files to add as slides entries",
148+
"title": "Add Slidev markdown files as projects",
149149
"icon": "$(add)"
150150
},
151151
{
@@ -190,12 +190,6 @@
190190
"icon": "$(run-all)",
191191
"enablement": "slidev:hasActiveProject"
192192
},
193-
{
194-
"command": "slidev.stop-dev",
195-
"category": "Slidev",
196-
"title": "Stop the dev server",
197-
"icon": "$(stop-circle)"
198-
},
199193
{
200194
"command": "slidev.open-in-browser",
201195
"category": "Slidev",
@@ -206,25 +200,29 @@
206200
"command": "slidev.preview-prev-click",
207201
"category": "Slidev",
208202
"title": "Navigate to prev click in preview window",
209-
"icon": "$(arrow-left)"
203+
"icon": "$(arrow-left)",
204+
"enablement": "slidev:preview:has-prev-click"
210205
},
211206
{
212207
"command": "slidev.preview-next-click",
213208
"category": "Slidev",
214209
"title": "Navigate to next click in preview window",
215-
"icon": "$(arrow-right)"
210+
"icon": "$(arrow-right)",
211+
"enablement": "slidev:preview:has-next-click"
216212
},
217213
{
218214
"command": "slidev.preview-prev-slide",
219215
"category": "Slidev",
220216
"title": "Navigate to prev slide in preview window",
221-
"icon": "$(arrow-up)"
217+
"icon": "$(arrow-up)",
218+
"enablement": "slidev:preview:has-prev-slide"
222219
},
223220
{
224221
"command": "slidev.preview-next-slide",
225222
"category": "Slidev",
226223
"title": "Navigate to next slide in preview window",
227-
"icon": "$(arrow-down)"
224+
"icon": "$(arrow-down)",
225+
"enablement": "slidev:preview:has-next-slide"
228226
},
229227
{
230228
"command": "slidev.enable-preview-sync",
@@ -331,11 +329,6 @@
331329
"command": "slidev.remove-entry",
332330
"when": "view == slidev-projects-tree && viewItem =~ /<project>/",
333331
"group": "inline@2"
334-
},
335-
{
336-
"command": "slidev.stop-dev",
337-
"when": "view == slidev-projects-tree && viewItem =~ /<up>/",
338-
"group": "inline"
339332
}
340333
]
341334
},
@@ -616,11 +609,13 @@
616609
"get-port-please": "catalog:prod",
617610
"mlly": "catalog:prod",
618611
"ovsx": "catalog:dev",
612+
"picomatch": "catalog:vscode",
619613
"prettier": "catalog:vscode",
620614
"reactive-vscode": "catalog:vscode",
621615
"tm-grammars": "catalog:frontend",
622616
"ts-json-schema-generator": "catalog:vscode",
623617
"volar-service-prettier": "catalog:vscode",
624-
"volar-service-yaml": "catalog:vscode"
618+
"volar-service-yaml": "catalog:vscode",
619+
"yaml-language-server": "catalog:vscode"
625620
}
626621
}

packages/vscode/src/commands.ts

Lines changed: 54 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { relative } from 'node:path'
22
import { slash } from '@antfu/utils'
33
import { useCommand } from 'reactive-vscode'
4-
import { Position, Range, Selection, TextEditorRevealType, Uri, window, workspace } from 'vscode'
4+
import { ConfigurationTarget, window, workspace } from 'vscode'
55
import { useDevServer } from './composables/useDevServer'
6-
import { useEditingSlideSource } from './composables/useEditingSlideSource'
7-
import { useFocusedSlideNo } from './composables/useFocusedSlideNo'
6+
import { useFocusedSlide } from './composables/useFocusedSlide'
87
import { configuredPort, forceEnabled, include, previewSync } from './configs'
9-
import { activeEntry, activeProject, activeSlidevData, addProject, projects, rescanProjects } from './projects'
8+
import { activeEntry, activeProject, addProject, projects, rescanProjects } from './projects'
109
import { findPossibleEntries } from './utils/findPossibleEntries'
10+
import { getSlidesTitle } from './utils/getSlidesTitle'
1111
import { usePreviewWebview } from './views/previewWebview'
1212

1313
export function useCommands() {
@@ -17,14 +17,32 @@ export function useCommands() {
1717
useCommand('slidev.rescan-projects', rescanProjects)
1818

1919
useCommand('slidev.choose-entry', async () => {
20-
const entry = await window.showQuickPick([...projects.keys()], {
21-
title: 'Choose active slides entry.',
22-
})
23-
if (entry)
24-
activeEntry.value = entry
20+
while (true) {
21+
const addNewEntry = '$(add) Add new slides entry...'
22+
const entry = await window.showQuickPick(
23+
[...projects.keys(), addNewEntry],
24+
{
25+
title: 'Choose active slides entry.',
26+
},
27+
)
28+
if (entry === addNewEntry) {
29+
if (!await addEntry()) {
30+
break
31+
}
32+
}
33+
else if (entry) {
34+
activeEntry.value = entry
35+
break
36+
}
37+
else {
38+
break
39+
}
40+
}
2541
})
2642

27-
useCommand('slidev.add-entry', async () => {
43+
useCommand('slidev.add-entry', addEntry)
44+
45+
async function addEntry() {
2846
const files = await findPossibleEntries()
2947
const selected = await window.showQuickPick(files, {
3048
title: 'Choose Markdown files to add as slides entries.',
@@ -37,10 +55,11 @@ export function useCommands() {
3755
const workspaceRoot = workspace.workspaceFolders[0].uri.fsPath
3856
const relatives = selected.map(s => slash(relative(workspaceRoot, s)))
3957
// write back to settings.json
40-
include.update([...include.value, ...relatives])
58+
await include.update([...include.value, ...relatives])
4159
}
4260
}
43-
})
61+
return !!selected
62+
}
4463

4564
useCommand('slidev.remove-entry', async (node: any) => {
4665
const entry = slash(node.treeItem.resourceUri.fsPath)
@@ -54,66 +73,35 @@ export function useCommands() {
5473
activeEntry.value = entry
5574
})
5675

57-
useCommand('slidev.stop-dev', async (node: any) => {
58-
const entry = node ? slash(node.treeItem.resourceUri.fsPath) : activeEntry.value
59-
if (!entry)
60-
return
61-
const project = projects.get(entry)
62-
const { stop } = useDevServer(project!)
63-
stop()
76+
useCommand('slidev.goto', (filepath: string, index: number) => {
77+
const { gotoSlide } = useFocusedSlide()
78+
gotoSlide(filepath, index)
6479
})
65-
66-
async function gotoSlide(filepath: string, index: number, getNo?: () => number | null) {
67-
const { markdown: currrentMarkdown, index: currentIndex } = useEditingSlideSource()
68-
const sameFile = currrentMarkdown.value?.filepath === filepath
69-
if (sameFile && currentIndex.value === index)
70-
return
71-
72-
const slide = activeSlidevData.value?.markdownFiles[filepath]?.slides[index]
73-
if (!slide)
74-
return
75-
76-
const uri = Uri.file(filepath).with({
77-
// Add a fragment to the URI will cause a flush. So we need to remove it if it's the same file.
78-
fragment: sameFile ? undefined : `L${slide.contentStart + 1}`,
79-
})
80-
const editor = await window.showTextDocument(await workspace.openTextDocument(uri))
81-
82-
const cursorPos = new Position(slide.contentStart, 0)
83-
editor.selection = new Selection(cursorPos, cursorPos)
84-
85-
const startPos = new Position(slide.start, 0)
86-
const endPos = new Position(slide.end, 0)
87-
const slideRange = new Range(startPos, endPos)
88-
editor.revealRange(slideRange, TextEditorRevealType.AtTop)
89-
90-
const no = getNo?.()
91-
if (no) {
92-
const focusedSlideNo = useFocusedSlideNo()
93-
focusedSlideNo.value = no
94-
}
95-
}
96-
97-
useCommand('slidev.goto', gotoSlide)
9880
useCommand('slidev.next', () => {
99-
const { markdown, index } = useEditingSlideSource()
100-
const focusedSlideNo = useFocusedSlideNo()
101-
gotoSlide(markdown.value!.filepath, index.value + 1, () => focusedSlideNo.value + 1)
81+
const { focusedMarkdown, focusedSourceSlide, gotoSlide } = useFocusedSlide()
82+
if (!focusedMarkdown.value || focusedSourceSlide.value == null)
83+
return
84+
gotoSlide(focusedMarkdown.value.filepath, focusedSourceSlide.value.index + 1)
10285
})
10386
useCommand('slidev.prev', () => {
104-
const { markdown, index } = useEditingSlideSource()
105-
const focusedSlideNo = useFocusedSlideNo()
106-
gotoSlide(markdown.value!.filepath, index.value - 1, () => focusedSlideNo.value - 1)
87+
const { focusedMarkdown, focusedSourceSlide, gotoSlide } = useFocusedSlide()
88+
if (!focusedMarkdown.value || focusedSourceSlide.value == null)
89+
return
90+
gotoSlide(focusedMarkdown.value.filepath, focusedSourceSlide.value.index - 1)
10791
})
10892

109-
useCommand('slidev.refresh-preview', () => {
93+
useCommand('slidev.refresh-preview', async () => {
11094
const { refresh } = usePreviewWebview()
111-
refresh()
95+
await refresh()
11296
})
11397

11498
useCommand('slidev.config-port', async () => {
99+
if (!activeProject.value) {
100+
window.showErrorMessage('No active project to configure port.')
101+
return
102+
}
115103
const port = await window.showInputBox({
116-
prompt: 'Slidev Preview Port',
104+
prompt: `Slidev Preview Port for ${getSlidesTitle(activeProject.value.data)}`,
117105
value: configuredPort.value.toString(),
118106
validateInput: (v) => {
119107
if (!v.match(/^\d+$/))
@@ -123,9 +111,9 @@ export function useCommands() {
123111
return null
124112
},
125113
})
126-
if (!port)
127-
return
128-
configuredPort.value = +port
114+
if (port && activeProject.value) {
115+
activeProject.value.port.value = +port
116+
}
129117
})
130118

131119
useCommand('slidev.start-dev', async () => {
@@ -138,12 +126,6 @@ export function useCommands() {
138126
const { start, showTerminal } = useDevServer(project)
139127
start()
140128
showTerminal()
141-
142-
const { retry } = usePreviewWebview()
143-
setTimeout(retry, 3000)
144-
setTimeout(retry, 5000)
145-
setTimeout(retry, 7000)
146-
setTimeout(retry, 9000)
147129
})
148130

149131
useCommand('slidev.open-in-browser', () => usePreviewWebview().openExternal())
@@ -153,6 +135,6 @@ export function useCommands() {
153135
useCommand('slidev.preview-prev-slide', () => usePreviewWebview().prevSlide())
154136
useCommand('slidev.preview-next-slide', () => usePreviewWebview().nextSlide())
155137

156-
useCommand('slidev.enable-preview-sync', () => (previewSync.value = true))
157-
useCommand('slidev.disable-preview-sync', () => (previewSync.value = false))
138+
useCommand('slidev.enable-preview-sync', () => (previewSync.update(true, ConfigurationTarget.Global)))
139+
useCommand('slidev.disable-preview-sync', () => (previewSync.update(false, ConfigurationTarget.Global)))
158140
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { computed, shallowRef, watch } from 'reactive-vscode'
2+
3+
export function useDebouncedComputed<T>(source: () => T, delay: (newVal: T, oldVal: T) => number | null) {
4+
const result = shallowRef(source())
5+
let timeout: NodeJS.Timeout | undefined
6+
watch(
7+
source,
8+
(newVal, oldVal) => {
9+
clearTimeout(timeout)
10+
const d = delay(newVal, oldVal)
11+
if (d == null) {
12+
result.value = newVal
13+
}
14+
else {
15+
timeout = setTimeout(() => {
16+
result.value = newVal
17+
}, d)
18+
}
19+
},
20+
)
21+
return computed<T>(() => result.value)
22+
}

0 commit comments

Comments
 (0)