Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/client/composables/useEmbeddedCtrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function useEmbeddedControl() {
)
},
{
throttle: 300,
throttle: 50,
immediate: true,
},
)
Expand Down
20 changes: 14 additions & 6 deletions packages/parser/src/fs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { PreparserExtensionLoader, SlideInfo, SlidevData, SlidevMarkdown, SlidevPreparserExtension, SourceSlideInfo } from '@slidev/types'
import fs from 'node:fs'
import { existsSync } from 'node:fs'
import { readFile, writeFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { slash } from '@antfu/utils'
import YAML from 'yaml'
Expand All @@ -19,8 +20,15 @@ export function injectPreparserExtensionLoader(fn: PreparserExtensionLoader) {
*/
export type LoadedSlidevData = Omit<SlidevData, 'config' | 'themeMeta'>

export async function load(userRoot: string, filepath: string, loadedSource: Record<string, string> = {}, mode?: string): Promise<LoadedSlidevData> {
const markdown = loadedSource[filepath] ?? fs.readFileSync(filepath, 'utf-8')
export async function load(
userRoot: string,
filepath: string,
sources: Record<string, string> | ((path: string) => Promise<string>) = {},
mode?: string,
): Promise<LoadedSlidevData> {
const loadSource = typeof sources === 'function' ? sources : async (path: string) => sources[path] ?? readFile(path, 'utf-8')

const markdown = await loadSource(filepath)

let extensions: SlidevPreparserExtension[] | undefined
if (preparserExtensionLoader) {
Expand All @@ -46,7 +54,7 @@ export async function load(userRoot: string, filepath: string, loadedSource: Rec
async function loadMarkdown(path: string, range?: string, frontmatterOverride?: Record<string, unknown>, importers?: SourceSlideInfo[]) {
let md = markdownFiles[path]
if (!md) {
const raw = loadedSource[path] ?? fs.readFileSync(path, 'utf-8')
const raw = await loadSource(path)
md = await parse(raw, path, extensions)
markdownFiles[path] = md
watchFiles[path] = new Set()
Expand Down Expand Up @@ -90,7 +98,7 @@ export async function load(userRoot: string, filepath: string, loadedSource: Rec
}
delete frontmatterOverride.src

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

export async function save(markdown: SlidevMarkdown) {
const fileContent = stringify(markdown)
fs.writeFileSync(markdown.filepath, fileContent, 'utf-8')
await writeFile(markdown.filepath, fileContent, 'utf-8')
return fileContent
}
4 changes: 2 additions & 2 deletions packages/types/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ export interface SourceSlideInfo extends SlideInfoBase {
*/
contentRaw: string
/**
* Slides import by this slide.
* Slides imported by this slide.
*/
imports?: SourceSlideInfo[]
frontmatterDoc?: YAML.Document
frontmatterDoc?: YAML.Document<YAML.Node, true>
frontmatterStyle?: FrontmatterStyle
}

Expand Down
1 change: 0 additions & 1 deletion packages/vscode/language-server/volar-service-yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ export function create({
sendError: noop,
sendTrack: noop,
},
// @ts-expect-error https://github.com/redhat-developer/yaml-language-server/pull/910
clientCapabilities: context.env?.clientCapabilities,
workspaceContext: getWorkspaceContextService(context),
})
Expand Down
31 changes: 13 additions & 18 deletions packages/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"vscode": "^1.99.0"
},
"activationEvents": [
"onStartupFinished"
"workspaceContains:**/*.md"
],
"contributes": {
"languages": [
Expand Down Expand Up @@ -145,7 +145,7 @@
{
"command": "slidev.add-entry",
"category": "Slidev",
"title": "Choose Markdown files to add as slides entries",
"title": "Add Slidev markdown files as projects",
"icon": "$(add)"
},
{
Expand Down Expand Up @@ -190,12 +190,6 @@
"icon": "$(run-all)",
"enablement": "slidev:hasActiveProject"
},
{
"command": "slidev.stop-dev",
"category": "Slidev",
"title": "Stop the dev server",
"icon": "$(stop-circle)"
},
{
"command": "slidev.open-in-browser",
"category": "Slidev",
Expand All @@ -206,25 +200,29 @@
"command": "slidev.preview-prev-click",
"category": "Slidev",
"title": "Navigate to prev click in preview window",
"icon": "$(arrow-left)"
"icon": "$(arrow-left)",
"enablement": "slidev:preview:has-prev-click"
},
{
"command": "slidev.preview-next-click",
"category": "Slidev",
"title": "Navigate to next click in preview window",
"icon": "$(arrow-right)"
"icon": "$(arrow-right)",
"enablement": "slidev:preview:has-next-click"
},
{
"command": "slidev.preview-prev-slide",
"category": "Slidev",
"title": "Navigate to prev slide in preview window",
"icon": "$(arrow-up)"
"icon": "$(arrow-up)",
"enablement": "slidev:preview:has-prev-slide"
},
{
"command": "slidev.preview-next-slide",
"category": "Slidev",
"title": "Navigate to next slide in preview window",
"icon": "$(arrow-down)"
"icon": "$(arrow-down)",
"enablement": "slidev:preview:has-next-slide"
},
{
"command": "slidev.enable-preview-sync",
Expand Down Expand Up @@ -331,11 +329,6 @@
"command": "slidev.remove-entry",
"when": "view == slidev-projects-tree && viewItem =~ /<project>/",
"group": "inline@2"
},
{
"command": "slidev.stop-dev",
"when": "view == slidev-projects-tree && viewItem =~ /<up>/",
"group": "inline"
}
]
},
Expand Down Expand Up @@ -616,11 +609,13 @@
"get-port-please": "catalog:prod",
"mlly": "catalog:prod",
"ovsx": "catalog:dev",
"picomatch": "catalog:vscode",
"prettier": "catalog:vscode",
"reactive-vscode": "catalog:vscode",
"tm-grammars": "catalog:frontend",
"ts-json-schema-generator": "catalog:vscode",
"volar-service-prettier": "catalog:vscode",
"volar-service-yaml": "catalog:vscode"
"volar-service-yaml": "catalog:vscode",
"yaml-language-server": "catalog:vscode"
}
}
112 changes: 54 additions & 58 deletions packages/vscode/src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { relative } from 'node:path'
import { slash } from '@antfu/utils'
import { useCommand } from 'reactive-vscode'
import { Position, Range, Selection, TextEditorRevealType, Uri, window, workspace } from 'vscode'
import { ConfigurationTarget, window, workspace } from 'vscode'
import { useDevServer } from './composables/useDevServer'
import { useEditingSlideSource } from './composables/useEditingSlideSource'
import { useFocusedSlideNo } from './composables/useFocusedSlideNo'
import { useFocusedSlide } from './composables/useFocusedSlide'
import { configuredPort, forceEnabled, include, previewSync } from './configs'
import { activeEntry, activeProject, activeSlidevData, addProject, projects, rescanProjects } from './projects'
import { activeEntry, activeProject, addProject, projects, rescanProjects } from './projects'
import { findPossibleEntries } from './utils/findPossibleEntries'
import { getSlidesTitle } from './utils/getSlidesTitle'
import { usePreviewWebview } from './views/previewWebview'

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

useCommand('slidev.choose-entry', async () => {
const entry = await window.showQuickPick([...projects.keys()], {
title: 'Choose active slides entry.',
})
if (entry)
activeEntry.value = entry
while (true) {
const addNewEntry = '$(add) Add new slides entry...'
const entry = await window.showQuickPick(
[...projects.keys(), addNewEntry],
{
title: 'Choose active slides entry.',
},
)
if (entry === addNewEntry) {
if (!await addEntry()) {
break
}
}
else if (entry) {
activeEntry.value = entry
break
}
else {
break
}
}
})

useCommand('slidev.add-entry', async () => {
useCommand('slidev.add-entry', addEntry)

async function addEntry() {
const files = await findPossibleEntries()
const selected = await window.showQuickPick(files, {
title: 'Choose Markdown files to add as slides entries.',
Expand All @@ -37,10 +55,11 @@ export function useCommands() {
const workspaceRoot = workspace.workspaceFolders[0].uri.fsPath
const relatives = selected.map(s => slash(relative(workspaceRoot, s)))
// write back to settings.json
include.update([...include.value, ...relatives])
await include.update([...include.value, ...relatives])
}
}
})
return !!selected
}

useCommand('slidev.remove-entry', async (node: any) => {
const entry = slash(node.treeItem.resourceUri.fsPath)
Expand All @@ -59,51 +78,24 @@ export function useCommands() {
if (!entry)
return
const project = projects.get(entry)
const { stop } = useDevServer(project!)
stop()
project?.server.value?.scope.stop()
})

async function gotoSlide(filepath: string, index: number, getNo?: () => number | null) {
const { markdown: currrentMarkdown, index: currentIndex } = useEditingSlideSource()
const sameFile = currrentMarkdown.value?.filepath === filepath
if (sameFile && currentIndex.value === index)
return

const slide = activeSlidevData.value?.markdownFiles[filepath]?.slides[index]
if (!slide)
return

const uri = Uri.file(filepath).with({
// Add a fragment to the URI will cause a flush. So we need to remove it if it's the same file.
fragment: sameFile ? undefined : `L${slide.contentStart + 1}`,
})
const editor = await window.showTextDocument(await workspace.openTextDocument(uri))

const cursorPos = new Position(slide.contentStart, 0)
editor.selection = new Selection(cursorPos, cursorPos)

const startPos = new Position(slide.start, 0)
const endPos = new Position(slide.end, 0)
const slideRange = new Range(startPos, endPos)
editor.revealRange(slideRange, TextEditorRevealType.AtTop)

const no = getNo?.()
if (no) {
const focusedSlideNo = useFocusedSlideNo()
focusedSlideNo.value = no
}
}

useCommand('slidev.goto', gotoSlide)
useCommand('slidev.goto', (filepath: string, index: number) => {
const { gotoSlide } = useFocusedSlide()
gotoSlide(filepath, index)
})
useCommand('slidev.next', () => {
const { markdown, index } = useEditingSlideSource()
const focusedSlideNo = useFocusedSlideNo()
gotoSlide(markdown.value!.filepath, index.value + 1, () => focusedSlideNo.value + 1)
const { focusedMarkdown, focusedSourceSlide, gotoSlide } = useFocusedSlide()
if (!focusedMarkdown.value || focusedSourceSlide.value == null)
return
gotoSlide(focusedMarkdown.value.filepath, focusedSourceSlide.value.index + 1)
})
useCommand('slidev.prev', () => {
const { markdown, index } = useEditingSlideSource()
const focusedSlideNo = useFocusedSlideNo()
gotoSlide(markdown.value!.filepath, index.value - 1, () => focusedSlideNo.value - 1)
const { focusedMarkdown, focusedSourceSlide, gotoSlide } = useFocusedSlide()
if (!focusedMarkdown.value || focusedSourceSlide.value == null)
return
gotoSlide(focusedMarkdown.value.filepath, focusedSourceSlide.value.index - 1)
})

useCommand('slidev.refresh-preview', () => {
Expand All @@ -112,8 +104,12 @@ export function useCommands() {
})

useCommand('slidev.config-port', async () => {
if (!activeProject.value) {
window.showErrorMessage('No active project to configure port.')
return
}
const port = await window.showInputBox({
prompt: 'Slidev Preview Port',
prompt: `Slidev Preview Port for ${getSlidesTitle(activeProject.value.data)}`,
value: configuredPort.value.toString(),
validateInput: (v) => {
if (!v.match(/^\d+$/))
Expand All @@ -123,9 +119,9 @@ export function useCommands() {
return null
},
})
if (!port)
return
configuredPort.value = +port
if (port && activeProject.value) {
activeProject.value.port.value = +port
}
})

useCommand('slidev.start-dev', async () => {
Expand Down Expand Up @@ -153,6 +149,6 @@ export function useCommands() {
useCommand('slidev.preview-prev-slide', () => usePreviewWebview().prevSlide())
useCommand('slidev.preview-next-slide', () => usePreviewWebview().nextSlide())

useCommand('slidev.enable-preview-sync', () => (previewSync.value = true))
useCommand('slidev.disable-preview-sync', () => (previewSync.value = false))
useCommand('slidev.enable-preview-sync', () => (previewSync.update(true, ConfigurationTarget.Global)))
useCommand('slidev.disable-preview-sync', () => (previewSync.update(false, ConfigurationTarget.Global)))
}
22 changes: 22 additions & 0 deletions packages/vscode/src/composables/useDebouncedComputed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { computed, shallowRef, watch } from 'reactive-vscode'

export function useDebouncedComputed<T>(source: () => T, delay: (newVal: T, oldVal: T) => number | null) {
const result = shallowRef(source())
let timeout: NodeJS.Timeout | undefined
watch(
source,
(newVal, oldVal) => {
clearTimeout(timeout)
const d = delay(newVal, oldVal)
if (d == null) {
result.value = newVal
}
else {
timeout = setTimeout(() => {
result.value = newVal
}, d)
}
},
)
return computed<T>(() => result.value)
}
Loading
Loading