Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vscode): slidev.include configuration #1675

Merged
merged 2 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions docs/guide/editors.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ In the **preview webview**, you can click the <codicon-run-all /> icon to start

There are also some **commands** you can use. Type `Slidev` in the command palette to see them.

You can add glob patterns to the `slidev.include` configuration to include files as Slidev entries. The default value is `["**/*.md"]`. Example:

```json
{
"slidev.include": ["**/presentation.md"]
}
```

## Prettier Plugin

Slidev also provides a Prettier plugin to format your slides. You can use it with your favorite editor that supports Prettier. Docs for the plugin can be found [here](https://github.com/slidevjs/prettier-plugin).
15 changes: 15 additions & 0 deletions packages/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,21 @@
"scope": "window",
"description": "Sync preview window with editor cursor",
"default": true
},
"slidev.include": {
"type": "array",
"scope": "window",
"description": "Glob patterns to include slides entries",
"items": {
"type": "string"
},
"default": ["**/slides.md"]
},
"slidev.exclude": {
"type": "string",
"scope": "window",
"description": "A glob pattern to exclude slides entries",
"default": "**/node_modules/**"
}
}
},
Expand Down
9 changes: 8 additions & 1 deletion packages/vscode/src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
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 { useDevServer } from './composables/useDevServer'
import { useEditingSlideSource } from './composables/useEditingSlideSource'
import { useFocusedSlideNo } from './composables/useFocusedSlideNo'
import { configuredPort, forceEnabled, previewSync } from './configs'
import { configuredPort, forceEnabled, include, previewSync } from './configs'
import type { SlidevProject } from './projects'
import { activeEntry, activeProject, activeSlidevData, addProject, projects, rescanProjects } from './projects'
import { findPossibleEntries } from './utils/findPossibleEntries'
Expand Down Expand Up @@ -41,6 +43,11 @@ export function useCommands() {
if (selected) {
for (const entry of selected)
await addProject(entry)
if (workspace.workspaceFolders) {
const workspaceRoot = workspace.workspaceFolders[0].uri.fsPath
const relatives = selected.map(s => slash(relative(workspaceRoot, s)))
include.value = [...include.value, ...relatives]
}
}
})

Expand Down
11 changes: 9 additions & 2 deletions packages/vscode/src/configs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
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'
Expand All @@ -9,15 +10,21 @@ const configKeys: Record<string, ShallowRef<unknown>> = {}

function useConfiguration<T>(key: string, defaultValue: T, writeBack = false): Ref<T> {
const r = configKeys[key] = shallowRef<T>(config.get<T>(key) ?? defaultValue)
if (writeBack)
watch(r, v => config.update(key, v))
if (writeBack) {
watch(r, (v) => {
if (!isDeepEqual(v, config.get<T>(key)))
config.update(key, v)
})
}
return r
}

export const forceEnabled = useConfiguration<boolean | null>('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<string[]>('include', ['**/slides.md'], true)
export const exclude = useConfiguration<string>('exclude', '**/node_modules/**')

export const isDarkTheme = ref(true)

Expand Down
56 changes: 22 additions & 34 deletions packages/vscode/src/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { existsSync } from 'node:fs'
import { basename, dirname } from 'node:path'
import type { LoadedSlidevData } from '@slidev/parser/fs'
import { load } from '@slidev/parser/fs'
import { computed, markRaw, onScopeDispose, reactive, ref, watchEffect } from '@vue/runtime-core'
import { commands, window, workspace } from 'vscode'
import { computed, markRaw, onScopeDispose, reactive, ref, watch, watchEffect } from '@vue/runtime-core'
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 { forceEnabled } from './configs'
import { exclude, forceEnabled, include } from './configs'

export interface SlidevProject {
readonly entry: string
Expand All @@ -23,17 +23,21 @@ export const activeProject = computed(() => activeEntry.value ? projects.get(act
export const activeSlidevData = computed(() => activeProject.value?.data)
export const activeUserRoot = computed(() => activeProject.value?.userRoot)

async function loadExistingProjects() {
const files = await workspace.findFiles('**/*.md', '**/node_modules/**')
async function addExistingProjects() {
const files = new Set<string>()
for (const glob of include.value) {
(await workspace.findFiles(glob, exclude.value))
.forEach(file => files.add(file.fsPath))
}
for (const file of files) {
const path = slash(file.fsPath)
if (basename(path) === 'slides.md')
const path = slash(file)
if (!projects.has(path))
(await addProjectEffect(path))()
}
}

export async function rescanProjects() {
await loadExistingProjects()
await addExistingProjects()
for (const project of projects.values()) {
if (!existsSync(project.entry)) {
projects.delete(project.entry)
Expand All @@ -48,7 +52,7 @@ export function useProjects() {
const logger = useLogger()

async function init() {
await loadExistingProjects()
await addExistingProjects()
await autoSetActiveEntry()
}
init()
Expand All @@ -69,55 +73,39 @@ export function useProjects() {

let pendingUpdate: { cancelled: boolean } | null = null

// TODO: Not sure why file creation is not being detected
const fsWatcher = workspace.createFileSystemWatcher('**/*.md')
onScopeDispose(() => fsWatcher.dispose())

fsWatcher.onDidChange(async (uri) => {
const path = slash(uri.fsPath)
const path = slash(uri.fsPath).toLowerCase()
logger.info(`File ${path} changed.`)
const startMs = Date.now()
pendingUpdate && (pendingUpdate.cancelled = true)
const thisUpdate = pendingUpdate = { cancelled: false }
const effects: (() => void)[] = []
let maybeNewEntry = path.endsWith('.md') && basename(path).toLowerCase() !== 'readme.md'
for (const project of projects.values()) {
if (project.data.watchFiles.includes(path))
maybeNewEntry = false
else
if (!project.data.watchFiles.some(f => f.toLowerCase() === path))
continue

if (existsSync(project.entry)) {
const newData = markRaw(await load(project.userRoot, project.entry))
maybeNewEntry &&= newData.watchFiles.includes(path)
effects.push(() => {
project.data = newData
logger.info(`Project ${project.entry} updated.`)
})
}
else {
effects.push(() => {
projects.delete(project.entry)
logger.info(`Project ${project.entry} removed.`)
if (activeEntry.value === project.entry) {
window.showWarningMessage('The active slides file has been deleted. Please choose another one.', 'Choose another one')
.then(result => result && commands.executeCommand('slidev.choose-entry'))
}
})
}

if (thisUpdate.cancelled)
return
}

if (basename(path).toLocaleLowerCase() === 'slides.md' && !projects.has(path))
effects.push(await addProjectEffect(path))

if (thisUpdate.cancelled)
return

effects.map(effect => effect())
autoSetActiveEntry()
logger.info(`All affected Slidev projects updated in ${Date.now() - startMs}ms.`)
})
onScopeDispose(() => fsWatcher.dispose())
fsWatcher.onDidCreate(rescanProjects)
fsWatcher.onDidDelete(rescanProjects)

watch(include, rescanProjects)
}

export async function addProject(entry: string) {
Expand Down
1 change: 0 additions & 1 deletion packages/vscode/src/utils/findPossibleEntries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export async function findPossibleEntries() {
.map(uri => slash(uri.fsPath))
.filter(path => !projects.has(path))
.filter(path => ![
'readme.md',
'code_of_conduct.md',
'contributing.md',
'license.md',
Expand Down
2 changes: 1 addition & 1 deletion packages/vscode/src/views/projectsTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const useProjectsTree = createSingletonComposable(() => {
return element
? typeof element === 'string'
? undefined
: element.data.watchFiles.filter(file => file !== element.entry)
: element.data.watchFiles.filter(file => file.toLowerCase() !== element.entry.toLowerCase())
: [...projects.values()]
},
},
Expand Down
Loading