diff --git a/client/extension.ts b/client/extension.ts index 21a95b1..3bcce35 100644 --- a/client/extension.ts +++ b/client/extension.ts @@ -23,6 +23,10 @@ export default class Extension { ...commands(), this.status, this.preview, + VSC.commands.registerCommand( + "markdoc.extractPartial", + this.onExtractPartial.bind(this) + ), VSC.commands.registerCommand( "markdoc.newFileFromTemplate", this.onNewFileFromTemplate.bind(this) @@ -173,6 +177,31 @@ export default class Extension { VSC.window.showTextDocument(doc); } + async onExtractPartial() { + const editor = VSC.window.activeTextEditor; + if (!editor) return; + + const uri = await VSC.window.showSaveDialog({ + saveLabel: 'Create', + title: 'Name the new partial', + filters: {'Markdoc': ['md', 'mdoc', 'markdoc', 'markdoc.md']} + }); + + if (!uri) return; + + const client = this.findClient(uri); + if (!client) return; + + const path = uri.fsPath.slice(client.uri.fsPath.length + 1); + const partialTag = `{% partial file="${path}" /%}`; + + const edit = new VSC.WorkspaceEdit(); + const contents = new TextEncoder().encode(editor.document.getText(editor.selection)); + edit.createFile(uri, {overwrite: true, contents}); + edit.replace(editor.document.uri, editor.selection, partialTag); + VSC.workspace.applyEdit(edit); + } + async onPreview(previewUri: VSC.Uri) { const uri = previewUri ?? VSC.window.activeTextEditor?.document.uri; if (!uri) return; diff --git a/client/package.json b/client/package.json index 1f16804..51338d3 100644 --- a/client/package.json +++ b/client/package.json @@ -3,7 +3,7 @@ "description": "Markdoc Extension", "author": "Ryan Paul", "license": "MIT", - "version": "0.0.10", + "version": "0.0.11", "scripts": { "build": "esbuild index.ts --bundle --outdir=dist --sourcemap=linked --external:vscode --platform=node --format=cjs", "build:server": "esbuild server.ts --bundle --outdir=dist --sourcemap=linked --external:vscode --platform=node --format=cjs" diff --git a/package.json b/package.json index 8b2c156..620089e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "author": "Ryan Paul", "publisher": "stripe", "license": "MIT", - "version": "0.0.10", + "version": "0.0.11", "description": "A Markdoc language server and Visual Studio Code extension", "repository": { "type": "git", diff --git a/server/package.json b/server/package.json index 3c59b8f..ecb427d 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@markdoc/language-server", - "version": "0.0.10", + "version": "0.0.11", "description": "A Markdoc language server", "main": "dist/index.js", "author": "Ryan Paul", diff --git a/server/plugins/codeaction.ts b/server/plugins/codeaction.ts index 2c96481..2b2cf7f 100644 --- a/server/plugins/codeaction.ts +++ b/server/plugins/codeaction.ts @@ -32,33 +32,75 @@ export default class CodeActionProvider { } } - findTables(ast: Markdoc.Node, params: LSP.CodeActionParams): LSP.CodeAction[] { + async inlinePartial(uri: string, line: number, file: string): Promise { + const ast = this.services.Documents.ast(uri); + if (!ast) return; + + for (const node of ast.walk()) { + if (node.tag === 'partial' && node.lines[0] === line && !node.attributes.variables) { + const fullPath = this.services.Scanner.fullPath(file); + const newText = await this.services.Scanner.read(fullPath); + const [start, end] = node.lines; + const range = LSP.Range.create(start, 0, end, 0); + return {changes: {[uri]: [{range, newText}]}}; + } + } + } + + findActions(ast: Markdoc.Node, params: LSP.CodeActionParams): LSP.CodeAction[] { const output: LSP.CodeAction[] = []; const {line} = params.range.start; const {uri} = params.textDocument; + + if (params.range.end.line - params.range.start.line > 3) + output.push({ + title: 'Extract content to new partial', + command: LSP.Command.create('Extract Partial', 'markdoc.extractPartial') + }); - for (const node of ast.walk()) - if (node.type === 'table' && node.lines.includes(line)) + for (const node of ast.walk()) { + if (node.type === 'table' && node.lines.includes(line)) { output.push({ data: {type: 'convertTable', uri, line}, title: 'Convert to Markdoc Table', }); + continue; + } + + if (node.tag === 'partial' && node.lines[0] === line && !node.attributes.variables) { + output.push({ + data: {type: 'inlinePartial', uri, line, file: node.attributes.file}, + title: 'Inline contents of this partial', + }); + + continue; + } + } + return output; } onCodeAction(params: LSP.CodeActionParams): (LSP.CodeAction | LSP.Command)[] { const ast = this.services.Documents.ast(params.textDocument.uri); - return ast ? this.findTables(ast, params) : []; + return ast ? this.findActions(ast, params) : []; } - onCodeActionResolve(action: LSP.CodeAction): LSP.CodeAction { + async onCodeActionResolve(action: LSP.CodeAction): Promise { + if (!action.data?.type) return action; + if (action.data.type === 'convertTable') { const {uri, line} = action.data; const edit = this.convertTable(uri, line); if (edit) return {...action, edit}; } + if (action.data.type === 'inlinePartial') { + const {uri, line, file} = action.data; + const edit = await this.inlinePartial(uri, line, file); + if (edit) return {...action, edit}; + } + return action; } } \ No newline at end of file