2023-09-05 16:02:27 -07:00
|
|
|
import type {
|
|
|
|
Completion,
|
|
|
|
CompletionContext,
|
|
|
|
CompletionResult,
|
|
|
|
} from '@codemirror/autocomplete'
|
2024-06-30 14:30:44 -07:00
|
|
|
import { completeFromList, snippetCompletion } from '@codemirror/autocomplete'
|
2024-07-03 20:59:54 -07:00
|
|
|
import {
|
|
|
|
Facet,
|
|
|
|
StateEffect,
|
|
|
|
Extension,
|
|
|
|
Transaction,
|
|
|
|
Annotation,
|
|
|
|
} from '@codemirror/state'
|
2024-06-30 14:30:44 -07:00
|
|
|
import type {
|
|
|
|
ViewUpdate,
|
|
|
|
PluginValue,
|
|
|
|
PluginSpec,
|
|
|
|
ViewPlugin,
|
|
|
|
} from '@codemirror/view'
|
|
|
|
import { EditorView, Tooltip } from '@codemirror/view'
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
import type { PublishDiagnosticsParams } from 'vscode-languageserver-protocol'
|
|
|
|
import type * as LSP from 'vscode-languageserver-protocol'
|
2024-06-29 18:10:07 -07:00
|
|
|
import {
|
2024-06-30 14:30:44 -07:00
|
|
|
DiagnosticSeverity,
|
|
|
|
CompletionTriggerKind,
|
|
|
|
} from 'vscode-languageserver-protocol'
|
|
|
|
import { URI } from 'vscode-uri'
|
|
|
|
|
|
|
|
import { LanguageServerClient } from '../client'
|
|
|
|
import { CompletionItemKindMap } from './autocomplete'
|
|
|
|
import { addToken, SemanticToken } from './semantic-tokens'
|
2024-07-17 00:26:14 -04:00
|
|
|
import { posToOffset, formatMarkdownContents } from './util'
|
2024-07-03 22:06:52 -07:00
|
|
|
import lspAutocompleteExt from './autocomplete'
|
2024-06-30 14:30:44 -07:00
|
|
|
import lspHoverExt from './hover'
|
|
|
|
import lspFormatExt from './format'
|
|
|
|
import lspIndentExt from './indent'
|
|
|
|
import lspLintExt from './lint'
|
|
|
|
import lspSemanticTokensExt from './semantic-tokens'
|
2024-02-19 12:33:16 -08:00
|
|
|
|
|
|
|
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '')
|
2024-06-29 18:10:07 -07:00
|
|
|
export const docPathFacet = Facet.define<string, string>({
|
|
|
|
combine: useLast,
|
|
|
|
})
|
2024-02-19 12:33:16 -08:00
|
|
|
export const languageId = Facet.define<string, string>({ combine: useLast })
|
|
|
|
export const workspaceFolders = Facet.define<
|
|
|
|
LSP.WorkspaceFolder[],
|
|
|
|
LSP.WorkspaceFolder[]
|
|
|
|
>({ combine: useLast })
|
2023-09-05 16:02:27 -07:00
|
|
|
|
2024-07-03 20:59:54 -07:00
|
|
|
export enum LspAnnotation {
|
|
|
|
SemanticTokens = 'semantic-tokens',
|
|
|
|
FormatCode = 'format-code',
|
|
|
|
Diagnostics = 'diagnostics',
|
|
|
|
}
|
|
|
|
|
|
|
|
const lspEvent = Annotation.define<LspAnnotation>()
|
|
|
|
export const lspSemanticTokensEvent = lspEvent.of(LspAnnotation.SemanticTokens)
|
|
|
|
export const lspFormatCodeEvent = lspEvent.of(LspAnnotation.FormatCode)
|
|
|
|
export const lspDiagnosticsEvent = lspEvent.of(LspAnnotation.Diagnostics)
|
|
|
|
|
2024-06-30 14:30:44 -07:00
|
|
|
export interface LanguageServerOptions {
|
|
|
|
// We assume this is the main project directory, we are currently working in.
|
|
|
|
workspaceFolders: LSP.WorkspaceFolder[]
|
|
|
|
documentUri: string
|
|
|
|
allowHTMLContent: boolean
|
|
|
|
client: LanguageServerClient
|
|
|
|
processLspNotification?: (
|
|
|
|
plugin: LanguageServerPlugin,
|
|
|
|
notification: LSP.NotificationMessage
|
|
|
|
) => void
|
|
|
|
|
|
|
|
changesDelay?: number
|
2024-07-08 16:47:30 -07:00
|
|
|
|
|
|
|
doSemanticTokens?: boolean
|
|
|
|
doFoldingRanges?: boolean
|
2024-06-29 18:10:07 -07:00
|
|
|
}
|
2024-04-15 17:18:32 -07:00
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
export class LanguageServerPlugin implements PluginValue {
|
|
|
|
public client: LanguageServerClient
|
|
|
|
private documentVersion: number
|
2024-04-15 17:18:32 -07:00
|
|
|
private foldingRanges: LSP.FoldingRange[] | null = null
|
2024-06-29 18:10:07 -07:00
|
|
|
|
|
|
|
private previousSemanticTokens: SemanticToken[] = []
|
|
|
|
|
2024-06-30 14:30:44 -07:00
|
|
|
private allowHTMLContent: boolean = true
|
|
|
|
private changesDelay: number = 600
|
|
|
|
private processLspNotification?: (
|
|
|
|
plugin: LanguageServerPlugin,
|
|
|
|
notification: LSP.NotificationMessage
|
|
|
|
) => void
|
|
|
|
|
2024-07-08 16:47:30 -07:00
|
|
|
private doSemanticTokens: boolean = false
|
|
|
|
private doFoldingRanges: boolean = false
|
|
|
|
|
2024-07-11 16:05:19 -07:00
|
|
|
// When a doc update needs to be sent to the server, this holds the
|
|
|
|
// timeout handle for it. When null, the server has the up-to-date
|
|
|
|
// document.
|
|
|
|
private sendScheduled: number | null = null
|
2024-06-30 14:30:44 -07:00
|
|
|
|
|
|
|
constructor(options: LanguageServerOptions, private view: EditorView) {
|
|
|
|
this.client = options.client
|
2023-09-05 16:02:27 -07:00
|
|
|
this.documentVersion = 0
|
|
|
|
|
2024-07-08 16:47:30 -07:00
|
|
|
this.doSemanticTokens = options.doSemanticTokens ?? false
|
|
|
|
this.doFoldingRanges = options.doFoldingRanges ?? false
|
|
|
|
|
2024-06-30 14:30:44 -07:00
|
|
|
if (options.changesDelay) {
|
|
|
|
this.changesDelay = options.changesDelay
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.allowHTMLContent !== undefined) {
|
|
|
|
this.allowHTMLContent = options.allowHTMLContent
|
|
|
|
}
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
this.client.attachPlugin(this)
|
|
|
|
|
2024-06-30 14:30:44 -07:00
|
|
|
this.processLspNotification = options.processLspNotification
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
this.initialize({
|
2024-06-29 18:10:07 -07:00
|
|
|
documentText: this.getDocText(),
|
2023-09-05 16:02:27 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-29 18:10:07 -07:00
|
|
|
private getDocPath(view = this.view) {
|
|
|
|
return view.state.facet(docPathFacet)
|
|
|
|
}
|
2024-06-30 14:30:44 -07:00
|
|
|
|
2024-06-29 18:10:07 -07:00
|
|
|
private getDocText(view = this.view) {
|
|
|
|
return view.state.doc.toString()
|
|
|
|
}
|
2024-04-22 17:21:24 -07:00
|
|
|
|
2024-06-29 18:10:07 -07:00
|
|
|
private getDocUri(view = this.view) {
|
|
|
|
return URI.file(this.getDocPath(view)).toString()
|
|
|
|
}
|
|
|
|
|
|
|
|
private getLanguageId(view = this.view) {
|
|
|
|
return view.state.facet(languageId)
|
|
|
|
}
|
|
|
|
|
|
|
|
update(viewUpdate: ViewUpdate) {
|
2024-07-11 16:05:19 -07:00
|
|
|
if (viewUpdate.docChanged) {
|
|
|
|
this.scheduleSendDoc()
|
2024-06-29 18:10:07 -07:00
|
|
|
}
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
destroy() {
|
|
|
|
this.client.detachPlugin(this)
|
|
|
|
}
|
|
|
|
|
|
|
|
async initialize({ documentText }: { documentText: string }) {
|
|
|
|
if (this.client.initializePromise) {
|
|
|
|
await this.client.initializePromise
|
|
|
|
}
|
2024-06-29 18:10:07 -07:00
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
this.client.textDocumentDidOpen({
|
|
|
|
textDocument: {
|
2024-06-29 18:10:07 -07:00
|
|
|
uri: this.getDocUri(),
|
|
|
|
languageId: this.getLanguageId(),
|
2023-09-05 16:02:27 -07:00
|
|
|
text: documentText,
|
|
|
|
version: this.documentVersion,
|
|
|
|
},
|
|
|
|
})
|
2024-06-29 18:10:07 -07:00
|
|
|
|
2024-06-30 14:30:44 -07:00
|
|
|
this.requestSemanticTokens()
|
|
|
|
this.updateFoldingRanges()
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async requestHoverTooltip(
|
|
|
|
view: EditorView,
|
|
|
|
{ line, character }: { line: number; character: number }
|
|
|
|
): Promise<Tooltip | null> {
|
|
|
|
if (
|
|
|
|
!this.client.ready ||
|
|
|
|
!this.client.getServerCapabilities().hoverProvider
|
|
|
|
)
|
|
|
|
return null
|
|
|
|
|
2024-07-11 16:05:19 -07:00
|
|
|
this.ensureDocSent()
|
2023-09-05 16:02:27 -07:00
|
|
|
const result = await this.client.textDocumentHover({
|
2024-06-29 18:10:07 -07:00
|
|
|
textDocument: { uri: this.getDocUri() },
|
2023-09-05 16:02:27 -07:00
|
|
|
position: { line, character },
|
|
|
|
})
|
|
|
|
if (!result) return null
|
|
|
|
const { contents, range } = result
|
|
|
|
let pos = posToOffset(view.state.doc, { line, character })!
|
|
|
|
let end: number | undefined
|
|
|
|
if (range) {
|
|
|
|
pos = posToOffset(view.state.doc, range.start)!
|
|
|
|
end = posToOffset(view.state.doc, range.end)
|
|
|
|
}
|
|
|
|
if (pos === null) return null
|
|
|
|
const dom = document.createElement('div')
|
|
|
|
dom.classList.add('documentation')
|
2024-06-22 15:50:16 -07:00
|
|
|
dom.classList.add('hover-tooltip')
|
2024-05-02 16:31:33 -07:00
|
|
|
dom.style.zIndex = '99999999'
|
2024-06-30 14:30:44 -07:00
|
|
|
if (this.allowHTMLContent) dom.innerHTML = formatMarkdownContents(contents)
|
|
|
|
else dom.textContent = formatMarkdownContents(contents)
|
2023-09-05 16:02:27 -07:00
|
|
|
return { pos, end, create: (view) => ({ dom }), above: true }
|
|
|
|
}
|
|
|
|
|
2024-07-11 16:05:19 -07:00
|
|
|
scheduleSendDoc() {
|
|
|
|
if (this.sendScheduled != null) window.clearTimeout(this.sendScheduled)
|
|
|
|
this.sendScheduled = window.setTimeout(
|
|
|
|
() => this.sendDoc(),
|
|
|
|
this.changesDelay
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
sendDoc() {
|
|
|
|
if (this.sendScheduled != null) {
|
|
|
|
window.clearTimeout(this.sendScheduled)
|
|
|
|
this.sendScheduled = null
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.client.ready) return
|
|
|
|
try {
|
|
|
|
// Update the state (not the editor) with the new code.
|
|
|
|
this.client.textDocumentDidChange({
|
|
|
|
textDocument: {
|
|
|
|
uri: this.getDocUri(),
|
|
|
|
version: this.documentVersion++,
|
|
|
|
},
|
|
|
|
contentChanges: [{ text: this.view.state.doc.toString() }],
|
|
|
|
})
|
|
|
|
|
|
|
|
this.requestSemanticTokens()
|
|
|
|
this.updateFoldingRanges()
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ensureDocSent() {
|
|
|
|
if (this.sendScheduled != null) this.sendDoc()
|
|
|
|
}
|
|
|
|
|
2024-04-15 17:18:32 -07:00
|
|
|
async getFoldingRanges(): Promise<LSP.FoldingRange[] | null> {
|
|
|
|
if (
|
2024-07-08 16:47:30 -07:00
|
|
|
!this.doFoldingRanges ||
|
2024-04-15 17:18:32 -07:00
|
|
|
!this.client.ready ||
|
|
|
|
!this.client.getServerCapabilities().foldingRangeProvider
|
|
|
|
)
|
|
|
|
return null
|
2024-06-30 14:30:44 -07:00
|
|
|
|
2024-04-15 17:18:32 -07:00
|
|
|
const result = await this.client.textDocumentFoldingRange({
|
2024-06-29 18:10:07 -07:00
|
|
|
textDocument: { uri: this.getDocUri() },
|
2024-04-15 17:18:32 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
return result || null
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateFoldingRanges() {
|
|
|
|
const foldingRanges = await this.getFoldingRanges()
|
|
|
|
if (foldingRanges === null) return
|
|
|
|
// Update the folding ranges.
|
|
|
|
this.foldingRanges = foldingRanges
|
|
|
|
}
|
|
|
|
|
|
|
|
// In the future if codemirrors foldService accepts async folding ranges
|
|
|
|
// then we will not have to store these and we can call getFoldingRanges
|
|
|
|
// here.
|
|
|
|
foldingRange(
|
|
|
|
lineStart: number,
|
|
|
|
lineEnd: number
|
|
|
|
): { from: number; to: number } | null {
|
|
|
|
if (this.foldingRanges === null) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < this.foldingRanges.length; i++) {
|
|
|
|
const { startLine, endLine } = this.foldingRanges[i]
|
|
|
|
if (startLine === lineEnd) {
|
|
|
|
const range = {
|
|
|
|
// Set the fold start to the end of the first line
|
|
|
|
// With this, the fold will not include the first line
|
|
|
|
from: startLine,
|
|
|
|
to: endLine,
|
|
|
|
}
|
|
|
|
|
|
|
|
return range
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
async requestFormatting() {
|
|
|
|
if (
|
|
|
|
!this.client.ready ||
|
|
|
|
!this.client.getServerCapabilities().documentFormattingProvider
|
|
|
|
)
|
|
|
|
return null
|
|
|
|
|
2024-07-11 16:05:19 -07:00
|
|
|
this.ensureDocSent()
|
2024-04-15 17:18:32 -07:00
|
|
|
|
|
|
|
const result = await this.client.textDocumentFormatting({
|
2024-06-29 18:10:07 -07:00
|
|
|
textDocument: { uri: this.getDocUri() },
|
2024-04-15 17:18:32 -07:00
|
|
|
options: {
|
|
|
|
tabSize: 2,
|
|
|
|
insertSpaces: true,
|
|
|
|
insertFinalNewline: true,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2024-07-03 19:28:46 -07:00
|
|
|
if (!result || !result.length) return null
|
|
|
|
|
|
|
|
this.view.dispatch({
|
|
|
|
changes: result.map(({ range, newText }) => ({
|
|
|
|
from: posToOffset(this.view.state.doc, range.start)!,
|
|
|
|
to: posToOffset(this.view.state.doc, range.end)!,
|
|
|
|
insert: newText,
|
|
|
|
})),
|
|
|
|
annotations: lspFormatCodeEvent,
|
|
|
|
})
|
2024-04-15 17:18:32 -07:00
|
|
|
}
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
async requestCompletion(
|
|
|
|
context: CompletionContext,
|
|
|
|
{ line, character }: { line: number; character: number },
|
|
|
|
{
|
|
|
|
triggerKind,
|
|
|
|
triggerCharacter,
|
|
|
|
}: {
|
|
|
|
triggerKind: CompletionTriggerKind
|
|
|
|
triggerCharacter: string | undefined
|
|
|
|
}
|
|
|
|
): Promise<CompletionResult | null> {
|
|
|
|
if (
|
|
|
|
!this.client.ready ||
|
|
|
|
!this.client.getServerCapabilities().completionProvider
|
|
|
|
)
|
|
|
|
return null
|
|
|
|
|
2024-07-11 16:05:19 -07:00
|
|
|
this.ensureDocSent()
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
const result = await this.client.textDocumentCompletion({
|
2024-06-29 18:10:07 -07:00
|
|
|
textDocument: { uri: this.getDocUri() },
|
2023-09-05 16:02:27 -07:00
|
|
|
position: { line, character },
|
|
|
|
context: {
|
|
|
|
triggerKind,
|
|
|
|
triggerCharacter,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!result) return null
|
|
|
|
|
|
|
|
const items = 'items' in result ? result.items : result
|
|
|
|
|
|
|
|
let options = items.map(
|
|
|
|
({
|
|
|
|
detail,
|
|
|
|
label,
|
|
|
|
labelDetails,
|
|
|
|
kind,
|
|
|
|
textEdit,
|
|
|
|
documentation,
|
|
|
|
deprecated,
|
|
|
|
insertText,
|
|
|
|
insertTextFormat,
|
|
|
|
sortText,
|
|
|
|
filterText,
|
|
|
|
}) => {
|
|
|
|
const completion: Completion & {
|
|
|
|
filterText: string
|
|
|
|
sortText?: string
|
|
|
|
apply: string
|
|
|
|
} = {
|
|
|
|
label,
|
|
|
|
detail: labelDetails ? labelDetails.detail : detail,
|
|
|
|
apply: label,
|
|
|
|
type: kind && CompletionItemKindMap[kind].toLowerCase(),
|
|
|
|
sortText: sortText ?? label,
|
|
|
|
filterText: filterText ?? label,
|
|
|
|
}
|
|
|
|
if (documentation) {
|
2023-09-06 21:27:30 -04:00
|
|
|
completion.info = () => {
|
2024-06-30 14:30:44 -07:00
|
|
|
const htmlString = formatMarkdownContents(documentation)
|
2023-09-06 21:27:30 -04:00
|
|
|
const htmlNode = document.createElement('div')
|
|
|
|
htmlNode.style.display = 'contents'
|
|
|
|
htmlNode.innerHTML = htmlString
|
|
|
|
return { dom: htmlNode }
|
|
|
|
}
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
2024-04-12 13:28:58 -07:00
|
|
|
if (insertText && insertTextFormat === 2) {
|
|
|
|
return snippetCompletion(insertText, completion)
|
|
|
|
}
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
return completion
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
return completeFromList(options)(context)
|
|
|
|
}
|
|
|
|
|
2024-06-29 18:10:07 -07:00
|
|
|
parseSemanticTokens(view: EditorView, data: number[]) {
|
|
|
|
// decode the lsp semantic token types
|
|
|
|
const tokens = []
|
|
|
|
for (let i = 0; i < data.length; i += 5) {
|
|
|
|
tokens.push({
|
|
|
|
deltaLine: data[i],
|
|
|
|
startChar: data[i + 1],
|
|
|
|
length: data[i + 2],
|
|
|
|
tokenType: data[i + 3],
|
|
|
|
modifiers: data[i + 4],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert the tokens into an array of {to, from, type} objects
|
|
|
|
const tokenTypes =
|
|
|
|
this.client.getServerCapabilities().semanticTokensProvider!.legend
|
|
|
|
.tokenTypes
|
|
|
|
const tokenModifiers =
|
|
|
|
this.client.getServerCapabilities().semanticTokensProvider!.legend
|
|
|
|
.tokenModifiers
|
|
|
|
const tokenRanges: any = []
|
|
|
|
let curLine = 0
|
|
|
|
let prevStart = 0
|
|
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
|
|
const token = tokens[i]
|
|
|
|
const tokenType = tokenTypes[token.tokenType]
|
|
|
|
// get a list of modifiers
|
|
|
|
const tokenModifier = []
|
|
|
|
for (let j = 0; j < tokenModifiers.length; j++) {
|
|
|
|
if (token.modifiers & (1 << j)) {
|
|
|
|
tokenModifier.push(tokenModifiers[j])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (token.deltaLine !== 0) prevStart = 0
|
|
|
|
|
|
|
|
const tokenRange = {
|
|
|
|
from: posToOffset(view.state.doc, {
|
|
|
|
line: curLine + token.deltaLine,
|
|
|
|
character: prevStart + token.startChar,
|
|
|
|
})!,
|
|
|
|
to: posToOffset(view.state.doc, {
|
|
|
|
line: curLine + token.deltaLine,
|
|
|
|
character: prevStart + token.startChar + token.length,
|
|
|
|
})!,
|
|
|
|
type: tokenType,
|
|
|
|
modifiers: tokenModifier,
|
|
|
|
}
|
|
|
|
tokenRanges.push(tokenRange)
|
|
|
|
|
|
|
|
curLine += token.deltaLine
|
|
|
|
prevStart += token.startChar
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort by from
|
|
|
|
tokenRanges.sort((a: any, b: any) => a.from - b.from)
|
|
|
|
return tokenRanges
|
|
|
|
}
|
|
|
|
|
2024-06-30 14:30:44 -07:00
|
|
|
async requestSemanticTokens() {
|
2024-06-29 18:10:07 -07:00
|
|
|
if (
|
2024-07-08 16:47:30 -07:00
|
|
|
!this.doSemanticTokens ||
|
2024-06-29 18:10:07 -07:00
|
|
|
!this.client.ready ||
|
|
|
|
!this.client.getServerCapabilities().semanticTokensProvider
|
|
|
|
) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
const result = await this.client.textDocumentSemanticTokensFull({
|
|
|
|
textDocument: { uri: this.getDocUri() },
|
|
|
|
})
|
|
|
|
if (!result) return null
|
|
|
|
|
|
|
|
const { data } = result
|
2024-06-30 14:30:44 -07:00
|
|
|
this.previousSemanticTokens = this.parseSemanticTokens(this.view, data)
|
2024-06-29 18:10:07 -07:00
|
|
|
|
|
|
|
const effects: StateEffect<SemanticToken | Extension>[] =
|
|
|
|
this.previousSemanticTokens.map((tokenRange: any) =>
|
|
|
|
addToken.of(tokenRange)
|
|
|
|
)
|
|
|
|
|
2024-06-30 14:30:44 -07:00
|
|
|
this.view.dispatch({
|
2024-06-29 18:10:07 -07:00
|
|
|
effects,
|
|
|
|
|
|
|
|
annotations: [lspSemanticTokensEvent, Transaction.addToHistory.of(false)],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-04-17 20:18:07 -07:00
|
|
|
async processNotification(notification: LSP.NotificationMessage) {
|
2023-09-05 16:02:27 -07:00
|
|
|
try {
|
|
|
|
switch (notification.method) {
|
|
|
|
case 'textDocument/publishDiagnostics':
|
2024-06-29 18:10:07 -07:00
|
|
|
if (notification === undefined) break
|
|
|
|
if (notification.params === undefined) break
|
|
|
|
if (!notification.params) break
|
|
|
|
const params = notification.params as PublishDiagnosticsParams
|
|
|
|
if (!params) break
|
2024-06-11 19:23:35 -04:00
|
|
|
console.log(
|
|
|
|
'[lsp] [window/publishDiagnostics]',
|
|
|
|
this.client.getName(),
|
2024-06-29 18:10:07 -07:00
|
|
|
params
|
2024-06-11 19:23:35 -04:00
|
|
|
)
|
2024-04-19 14:24:40 -07:00
|
|
|
// this is sometimes slower than our actual typing.
|
2024-06-11 19:23:35 -04:00
|
|
|
this.processDiagnostics(params)
|
2024-02-19 12:33:16 -08:00
|
|
|
break
|
|
|
|
case 'window/logMessage':
|
|
|
|
console.log(
|
|
|
|
'[lsp] [window/logMessage]',
|
|
|
|
this.client.getName(),
|
|
|
|
notification.params
|
|
|
|
)
|
|
|
|
break
|
|
|
|
case 'window/showMessage':
|
|
|
|
console.log(
|
|
|
|
'[lsp] [window/showMessage]',
|
|
|
|
this.client.getName(),
|
|
|
|
notification.params
|
|
|
|
)
|
|
|
|
break
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error)
|
|
|
|
}
|
2024-06-30 14:30:44 -07:00
|
|
|
|
|
|
|
// Send it to the plugin
|
|
|
|
this.processLspNotification?.(this, notification)
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
processDiagnostics(params: PublishDiagnosticsParams) {
|
2024-06-29 18:10:07 -07:00
|
|
|
if (params.uri !== this.getDocUri()) return
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
const diagnostics = params.diagnostics
|
|
|
|
.map(({ range, message, severity }) => ({
|
|
|
|
from: posToOffset(this.view.state.doc, range.start)!,
|
|
|
|
to: posToOffset(this.view.state.doc, range.end)!,
|
|
|
|
severity: (
|
|
|
|
{
|
|
|
|
[DiagnosticSeverity.Error]: 'error',
|
|
|
|
[DiagnosticSeverity.Warning]: 'warning',
|
|
|
|
[DiagnosticSeverity.Information]: 'info',
|
|
|
|
[DiagnosticSeverity.Hint]: 'info',
|
|
|
|
} as const
|
|
|
|
)[severity!],
|
|
|
|
message,
|
|
|
|
}))
|
|
|
|
.filter(
|
|
|
|
({ from, to }) =>
|
|
|
|
from !== null && to !== null && from !== undefined && to !== undefined
|
|
|
|
)
|
|
|
|
.sort((a, b) => {
|
|
|
|
switch (true) {
|
|
|
|
case a.from < b.from:
|
|
|
|
return -1
|
|
|
|
case a.from > b.from:
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
})
|
|
|
|
|
2024-06-30 14:30:44 -07:00
|
|
|
/* This creates infighting with the others.
|
|
|
|
* TODO: turn it back on when we have a better way to handle it.
|
|
|
|
* this.view.dispatch({
|
|
|
|
effects: [setDiagnosticsEffect.of(diagnostics)],
|
|
|
|
annotations: [lspDiagnosticsEvent, Transaction.addToHistory.of(false)],
|
|
|
|
})*/
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-30 14:30:44 -07:00
|
|
|
export class LanguageServerPluginSpec
|
|
|
|
implements PluginSpec<LanguageServerPlugin>
|
|
|
|
{
|
|
|
|
provide(plugin: ViewPlugin<LanguageServerPlugin>): Extension {
|
|
|
|
return [
|
2024-07-03 22:06:52 -07:00
|
|
|
lspAutocompleteExt(plugin),
|
2024-06-30 14:30:44 -07:00
|
|
|
lspFormatExt(plugin),
|
|
|
|
lspHoverExt(plugin),
|
|
|
|
lspIndentExt(),
|
|
|
|
lspLintExt(),
|
|
|
|
lspSemanticTokensExt(),
|
|
|
|
]
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
}
|