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

Change custom request types to LSP commands #473

Merged
merged 6 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions .changeset/large-shoes-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@mdx-js/language-service': minor
'@mdx-js/language-server': minor
---

Convert the custom MDX syntax toggle request types into LSP commands.
19 changes: 19 additions & 0 deletions packages/language-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,23 @@ This language server supports all features supported by
[`volar-service-typescript`][volar-service-typescript], plus some additional
features specific to MDX.

#### Commands

The language server supports the following [LSP commands][]:

* `mdx.toggleDelete` — Toggle delete syntax at the cursor position.
This takes the URI as its first argument, and the LSP selection range as its
second argument.
* `mdx.toggleEmphasis` — Toggle emphasis syntax at the cursor position.
This takes the URI as its first argument, and the LSP selection range as its
second argument.
* `mdx.toggleInlineCode` — Toggle inline code syntax at the cursor position.
This takes the URI as its first argument, and the LSP selection range as its
second argument.
* `mdx.toggleStrong` — Toggle strong syntax at the cursor position.
This takes the URI as its first argument, and the LSP selection range as its
second argument.

### Initialize Options

MDX language server supports the following LSP initialization options:
Expand Down Expand Up @@ -271,6 +288,8 @@ Detailed changes for each release are documented in [CHANGELOG.md](./CHANGELOG.m

[lsp]: https://microsoft.github.io/language-server-protocol

[lsp commands]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#command

[mdx]: https://mdxjs.com

[mit]: LICENSE
Expand Down
31 changes: 0 additions & 31 deletions packages/language-server/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env node

/**
* @import {Commands} from '@mdx-js/language-service'
* @import {PluggableList, Plugin} from 'unified'
*/

Expand All @@ -26,7 +25,6 @@ import remarkGfm from 'remark-gfm'
import {create as createMarkdownServicePlugin} from 'volar-service-markdown'
import {create as createTypeScriptServicePlugin} from 'volar-service-typescript'
import {create as createTypeScriptSyntacticServicePlugin} from 'volar-service-typescript/lib/plugins/syntactic.js'
import {URI} from 'vscode-uri'

process.title = 'mdx-language-server'

Expand Down Expand Up @@ -123,26 +121,6 @@ connection.onInitialize(async (parameters) => {
}
})

connection.onRequest('mdx/toggleDelete', async (parameters) => {
const commands = await getCommands(parameters.uri)
return commands.toggleDelete(parameters)
})

connection.onRequest('mdx/toggleEmphasis', async (parameters) => {
const commands = await getCommands(parameters.uri)
return commands.toggleEmphasis(parameters)
})

connection.onRequest('mdx/toggleInlineCode', async (parameters) => {
const commands = await getCommands(parameters.uri)
return commands.toggleInlineCode(parameters)
})

connection.onRequest('mdx/toggleStrong', async (parameters) => {
const commands = await getCommands(parameters.uri)
return commands.toggleStrong(parameters)
})

connection.onInitialized(() => {
const extensions = ['mdx']
if (tsEnabled) {
Expand All @@ -164,12 +142,3 @@ connection.onInitialized(() => {
})

connection.listen()

/**
* @param {string} uri
* @returns {Promise<Commands>}
*/
async function getCommands(uri) {
const service = await server.project.getLanguageService(URI.parse(uri))
return service.context.inject('mdxCommands')
}
8 changes: 8 additions & 0 deletions packages/language-server/test/initialize.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ test('initialize', async () => {
},
documentRangeFormattingProvider: true,
documentSymbolProvider: true,
executeCommandProvider: {
commands: [
'mdx.toggleDelete',
'mdx.toggleEmphasis',
'mdx.toggleInlineCode',
'mdx.toggleStrong'
]
},
experimental: {
autoInsertionProvider: {
configurationSections: [
Expand Down
55 changes: 30 additions & 25 deletions packages/language-server/test/syntax-toggle.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* @import {LanguageServerHandle} from '@volar/test-utils'
* @import {SyntaxToggleParams} from '@mdx-js/language-service'
* @import {Range} from '@volar/language-server'
*/

import assert from 'node:assert/strict'
import {afterEach, beforeEach, test} from 'node:test'
import {createServer, fixtureUri, tsdk} from './utils.js'
Expand All @@ -22,12 +23,13 @@ afterEach(() => {

test('delete', async () => {
await serverHandle.openInMemoryDocument('memory://1', 'mdx', 'Hello\n')
const result = await serverHandle.connection.sendRequest(
'mdx/toggleDelete',
/** @satisfies {SyntaxToggleParams} */ ({
uri: 'memory://1',
range: {end: {character: 3, line: 0}, start: {character: 3, line: 0}}
})
const result = await serverHandle.sendExecuteCommandRequest(
'mdx.toggleDelete',
[
'memory://1',
/** @satisfies {Range} */
({end: {character: 3, line: 0}, start: {character: 3, line: 0}})
]
)

assert.deepEqual(result, [
Expand All @@ -44,12 +46,13 @@ test('delete', async () => {

test('emphasis', async () => {
await serverHandle.openInMemoryDocument('memory://1', 'mdx', 'Hello\n')
const result = await serverHandle.connection.sendRequest(
'mdx/toggleEmphasis',
/** @satisfies {SyntaxToggleParams} */ ({
uri: 'memory://1',
range: {end: {character: 3, line: 0}, start: {character: 3, line: 0}}
})
const result = await serverHandle.sendExecuteCommandRequest(
'mdx.toggleEmphasis',
[
'memory://1',
/** @satisfies {Range} */
({end: {character: 3, line: 0}, start: {character: 3, line: 0}})
]
)

assert.deepEqual(result, [
Expand All @@ -66,12 +69,13 @@ test('emphasis', async () => {

test('inlineCode', async () => {
await serverHandle.openInMemoryDocument('memory://1', 'mdx', 'Hello\n')
const result = await serverHandle.connection.sendRequest(
'mdx/toggleInlineCode',
/** @satisfies {SyntaxToggleParams} */ ({
uri: 'memory://1',
range: {end: {character: 3, line: 0}, start: {character: 3, line: 0}}
})
const result = await serverHandle.sendExecuteCommandRequest(
'mdx.toggleInlineCode',
[
'memory://1',
/** @satisfies {Range} */
({end: {character: 3, line: 0}, start: {character: 3, line: 0}})
]
)

assert.deepEqual(result, [
Expand All @@ -88,12 +92,13 @@ test('inlineCode', async () => {

test('strong', async () => {
await serverHandle.openInMemoryDocument('memory://1', 'mdx', 'Hello\n')
const result = await serverHandle.connection.sendRequest(
'mdx/toggleStrong',
/** @satisfies {SyntaxToggleParams} */ ({
uri: 'memory://1',
range: {end: {character: 3, line: 0}, start: {character: 3, line: 0}}
})
const result = await serverHandle.sendExecuteCommandRequest(
'mdx.toggleStrong',
[
'memory://1',
/** @satisfies {Range} */
({end: {character: 3, line: 0}, start: {character: 3, line: 0}})
]
)

assert.deepEqual(result, [
Expand Down
18 changes: 16 additions & 2 deletions packages/language-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,22 @@ The service supports:

* Reporting diagnostics for parsing errors.
* Document drop support for images.
* Custom commands for toggling `delete`, `emphasis`, `inlineCode`, and `strong`
text.
* Custom commands.

The following commands are supported:

* `mdx.toggleDelete` — Toggle delete syntax at the cursor position.
This takes the URI as its first argument, and the LSP selection range as its
second argument.
* `mdx.toggleEmphasis` — Toggle emphasis syntax at the cursor position.
This takes the URI as its first argument, and the LSP selection range as its
second argument.
* `mdx.toggleInlineCode` — Toggle inline code syntax at the cursor position.
This takes the URI as its first argument, and the LSP selection range as its
second argument.
* `mdx.toggleStrong` — Toggle strong syntax at the cursor position.
This takes the URI as its first argument, and the LSP selection range as its
second argument.

#### Parameters

Expand Down
6 changes: 1 addition & 5 deletions packages/language-service/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
/**
* @typedef {import('./lib/commands.js').SyntaxToggleParams} SyntaxToggleParams
* @typedef {import('./lib/service-plugin.js').Commands} Commands
*/

export {commands} from './lib/commands.js'
export {createMdxLanguagePlugin} from './lib/language-plugin.js'
export {createMdxServicePlugin} from './lib/service-plugin.js'
export {resolveRemarkPlugins} from './lib/tsconfig.js'
32 changes: 17 additions & 15 deletions packages/language-service/lib/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,15 @@
* @import {Nodes} from 'mdast'
*/

/**
* @typedef SyntaxToggleParams
* The request parameters for LSP toggle requests.
* @property {string} uri
* The URI of the document the request is for.
* @property {Range} range
* The range that is selected by the user.
*/

/**
* @callback SyntaxToggle
* A function to toggle prose markdown syntax based on the AST.
* @param {SyntaxToggleParams} params
* The input parameters from the LSP request.
* @param {LanguageServiceContext} context
* The Volar service context.
* @param {string} uri
* The URI of the document the request is for.
* @param {Range} range
* The range that is selected by the user.
* @returns {TextEdit[] | undefined}
* LSP text edits that should be made.
*/
Expand All @@ -29,17 +24,15 @@ import {VirtualMdxCode} from './virtual-code.js'
/**
* Create a function to toggle prose syntax based on the AST.
*
* @param {LanguageServiceContext} context
* The Volar service context.
* @param {Nodes['type']} type
* The type of the mdast node to toggle.
* @param {string} separator
* The mdast node separator to insert.
* @returns {SyntaxToggle}
* An LSP based syntax toggle function.
*/
export function createSyntaxToggle(context, type, separator) {
return ({range, uri}) => {
function createSyntaxToggle(type, separator) {
return (context, uri, range) => {
const parsedUri = URI.parse(uri)
const sourceScript = context.language.scripts.get(parsedUri)
const root = sourceScript?.generated?.root
Expand Down Expand Up @@ -146,3 +139,12 @@ export function createSyntaxToggle(context, type, separator) {
}
}
}

export const implementations = {
'mdx.toggleDelete': createSyntaxToggle('delete', '~'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where’s this word from, delete? Perhaps strikethrough is better. Also, it’s a GFM-only feature. And, 2 tildes work there too. One tilde was forbidden by the GFM spec for a while, until recently, even though it worked.

Copy link
Member Author

@remcohaszing remcohaszing Sep 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete is the mdast node type. Based on your comment, I agree inserting 2 tildes is better. This changed be done in a follow-up PR though, as it’s an unrelated change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn’t particularly have to match the node type, as it’s injecting into a document.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s a toggle. It either injects or removes, depending on whether the cursor position matches a node of this type.

'mdx.toggleEmphasis': createSyntaxToggle('emphasis', '_'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn’t really matter. I know remark stringifies uses * in its output by default. I personally feel * is a bit overloaded. Emphasis and strong text share the same characters, but * is also used for list items. Also Prettier uses _, which I think added to making that character option more popular.

TL;DR I prefer _, but we can change it to * if you feel very strongly about it. This should be done in a follow-up PR though, as it’s an unrelated change.

Copy link
Member

@wooorm wooorm Sep 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does it not matter? Yes, it matters. _ and * get parsed differently. _ occurs more often in natural language.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both exist, but have relatively rare use cases in natural language. It’s really just a preference, and it’s ok your preference differs from mine.

I will change it in a follow-up PR anyway, as I believe the strongest argument is that it’s good to match the remark-stringify default. If we enable configurable formatting for #456, I think we should respect the same configuration optionn for syntax toggles.

'mdx.toggleInlineCode': createSyntaxToggle('inlineCode', '`'),
'mdx.toggleStrong': createSyntaxToggle('strong', '**')
}

export const commands = Object.keys(implementations)
37 changes: 13 additions & 24 deletions packages/language-service/lib/service-plugin.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
/**
* @import {DataTransferItem, LanguageServicePlugin} from '@volar/language-service'
* @import {SyntaxToggle} from './commands.js'
*/

/**
* @typedef Commands
* @property {SyntaxToggle} toggleDelete
* @property {SyntaxToggle} toggleEmphasis
* @property {SyntaxToggle} toggleInlineCode
* @property {SyntaxToggle} toggleStrong
*/

/**
* @typedef Provide
* @property {() => Commands} mdxCommands
* @import {DataTransferItem, LanguageServicePlugin, Range} from '@volar/language-service'
*/

import path from 'node:path/posix'
import {toMarkdown} from 'mdast-util-to-markdown'
import {fromPlace} from 'unist-util-lsp'
import {URI, Utils} from 'vscode-uri'
import {createSyntaxToggle} from './commands.js'
import {commands, implementations} from './commands.js'
import {VirtualMdxCode} from './virtual-code.js'

// https://github.com/microsoft/vscode/blob/1.83.1/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts#L29-L41
Expand Down Expand Up @@ -62,19 +48,22 @@ export function createMdxServicePlugin() {
interFileDependencies: false,
workspaceDiagnostics: false
},
executeCommandProvider: {
commands
},
documentDropEditsProvider: true
},

create(context) {
return {
provide: {
mdxCommands() {
return {
toggleDelete: createSyntaxToggle(context, 'delete', '~'),
toggleEmphasis: createSyntaxToggle(context, 'emphasis', '_'),
toggleInlineCode: createSyntaxToggle(context, 'inlineCode', '`'),
toggleStrong: createSyntaxToggle(context, 'strong', '**')
}
executeCommand(command, args) {
if (
command in implementations &&
Object.hasOwn(implementations, command)
) {
const fn =
implementations[/** @type {keyof implementations} */ (command)]
return fn(context, .../** @type {[string, Range]} */ (args))
}
},

Expand Down
1 change: 1 addition & 0 deletions packages/vscode-mdx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"vscode:prepublish": "node ./script/build.mjs"
},
"devDependencies": {
"@mdx-js/language-service": "^0.5.0",
"@types/node": "^22.0.0",
"@types/vscode": "^1.82.0",
"@volar/language-server": "~2.4.0",
Expand Down
Loading
Loading