Skip to content
Draft
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
3 changes: 2 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/vscode": "^1.86.0"
"@types/vscode": "^1.86.0",
"vscode-languageserver-textdocument": "^1.0.11"
}
}
29 changes: 27 additions & 2 deletions client/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as path from "path"

import { workspace, ExtensionContext } from "vscode"
import { workspace, languages, ExtensionContext, InlayHint, InlayHintKind, TextDocument, Range, CancellationToken } from "vscode"
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from "vscode-languageclient/node"

import { ControllerTreeView } from "./controller_tree_view"
import type { ControllerDefinitionsResponse } from "./requests"
import type { ControllerDefinitionsResponse, InlayHintsResponse } from "./requests"

export class Client {
private client: LanguageClient
Expand All @@ -30,6 +30,7 @@ export class Client {
try {
this.client.start()
this.context.subscriptions.push(new ControllerTreeView(this))
this.registerInlayHints()
} catch (error: any) {
console.error(`Error restarting the server: ${error.message}`)
return
Expand All @@ -54,6 +55,14 @@ export class Client {
return await this.sendRequest<ControllerDefinitionsResponse>("stimulus-lsp/controllerDefinitions", {})
}

async requestInlayHints(document: TextDocument, range: Range, token: CancellationToken): Promise<InlayHintsResponse> {
return await this.sendRequest<InlayHintsResponse>("stimulus-lsp/inlayHints", {
document,
range,
token,
})
}

// The debug options for the server
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
private get debugOptions() {
Expand Down Expand Up @@ -95,4 +104,20 @@ export class Client {
},
}
}

private registerInlayHints() {
languages.registerInlayHintsProvider("javascript", {
provideInlayHints: async (document, range, token) => {
const inlayHints = await this.requestInlayHints(document, range, token)

return inlayHints.map(({ text, position, tooltip }) => {
const hint = new InlayHint(position, text, InlayHintKind.Parameter)
hint.tooltip = tooltip

return hint
})
},
resolveInlayHint: (hint, _token) => hint,
})
}
}
14 changes: 13 additions & 1 deletion client/src/requests.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Position } from "vscode-languageclient"
import type { Position, TextDocument } from "vscode"

export type ControllerDefinition = {
identifier: string
Expand All @@ -24,3 +24,15 @@ export type ControllerDefinitionsResponse = {
nodeModules: ControllerDefinitionsOrigin[]
}
}

type InlayHint = {
text: string
tooltip: string
position: Position
}

export type InlayHintsRequest = {
document: TextDocument
}

export type InlayHintsResponse = InlayHint[]
5 changes: 5 additions & 0 deletions client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ vscode-languageserver-protocol@3.17.5:
vscode-jsonrpc "8.2.0"
vscode-languageserver-types "3.17.5"

vscode-languageserver-textdocument@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf"
integrity sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==

vscode-languageserver-types@3.17.5:
version "3.17.5"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a"
Expand Down
1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
},
"devDependencies": {
"@types/estree": "^1.0.5",
"@types/vscode": "^1.86.0",
"acorn": "^8.11.3",
"astring": "^1.8.6",
"rimraf": "^5.0.5",
Expand Down
114 changes: 114 additions & 0 deletions server/src/inlay_hints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { DocumentService } from "./document_service"
import { Project } from "stimulus-parser"

import { Position } from "vscode-languageserver"
import type { InlayHintsRequest, InlayHintsResponse } from "./requests"

export class InlayHints {
private readonly documentService: DocumentService
private readonly project: Project

constructor(documentService: DocumentService, project: Project) {
this.documentService = documentService
this.project = project
}

async onInlayHints({ document }: InlayHintsRequest): Promise<InlayHintsResponse> {
console.log(document.uri)

await new Promise((resolve) => setTimeout(resolve, 1000))

const textDocument = this.documentService.get(`file://${document.uri.fsPath}`)
if (!textDocument) return []

const projectFile = this.project.projectFiles.find((file) => `file://${file.path}` === textDocument.uri)
if (!projectFile || !projectFile.isProjectFile) return []

return projectFile.controllerDefinitions.flatMap((definition) => {
const hints = []

if (definition.values.length > 0 && definition.values.length > definition.localValues.length) {
const loc = definition.values[0].loc?.end

const nonLocalValues = definition.values.filter((value) => !definition.localValueNames.includes(value.name))

const values = nonLocalValues.map((value) => {
// TODO: this shouldn't be necessary
let defaultValue: any = value.default === undefined ? "undefined" : value.default
defaultValue = value.default === null ? "null" : value.default
defaultValue = value.default === false ? "false" : value.default
defaultValue = value.default === true ? "true" : value.default
defaultValue = value.default === "" ? '""' : value.default

return `"${value.name}": { type: ${value.type}, default: ${defaultValue} }`
})

if (loc && nonLocalValues.length > 0) {
hints.push({
text: `, ${values.join(", ")}`,
position: Position.create(loc.line - 1, loc.column - 1),
tooltip: "Inherited Value Definitions from parent controllers",
})
}
}

if (definition.targets.length > 0 && definition.targets.length > definition.localTargets.length) {
const loc = definition.targets[0].loc?.end

const nonLocalTargets = definition.targets.filter(
(target) => !definition.localTargetNames.includes(target.name),
)

if (loc && nonLocalTargets.length > 0) {
hints.push({
text: `, ${nonLocalTargets.map((target) => `"${target.name}"`).join(", ")}`,
position: Position.create(loc.line - 1, loc.column - 1),
tooltip: "Inherited Target Definitions from parent controllers",
})
}
}

if (definition.classes.length > definition.localClasses.length) {
const node = definition.classes[0].node
const position = { line: 0, character: 0 }
const nonLocalClasses = definition.classes.filter((klass) => !definition.localClassNames.includes(klass.name))

let text = nonLocalClasses.map((klass) => `"${klass.name}"`).join(", ")

if (node.loc && node.type === "ArrayExpression") {
const lastElementLoc = (node.elements || []).reverse()[0]?.loc

if (lastElementLoc && definition.localClasses.length > 0) {
text = `, ${text}`

if (node.loc.start.line === node.loc.end.line) {
position.line = lastElementLoc.start.line - 1
position.character = lastElementLoc.end.column
} else {
position.line = node.loc.end.line - 2
position.character = node.loc.end.column - 1
}
} else {
const loc = definition.classDeclaration.node?.loc
text = `static classes = [${text}]`

if (loc) {
position.line = loc.start.line
position.character = 2
}
}
}

if (nonLocalClasses.length > 0) {
hints.push({
text,
position: Position.create(position.line, position.character),
tooltip: "Inherited Class Definitions from parent controllers",
})
}
}

return hints
})
}
}
15 changes: 14 additions & 1 deletion server/src/requests.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Position } from "vscode-languageserver"
import type { Position } from "vscode-languageserver"
import type { TextDocument } from "vscode"

export type ControllerDefinition = {
identifier: string
Expand All @@ -24,3 +25,15 @@ export type ControllerDefinitionsResponse = {
nodeModules: ControllerDefinitionsOrigin[]
}
}


type InlayHint = {
text: string
tooltip: string
position: Position
}
export type InlayHintsRequest = {
document: TextDocument
}

export type InlayHintsResponse = InlayHint[]
5 changes: 5 additions & 0 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ connection.onInitialize(async (params: InitializeParams) => {
textDocumentSync: TextDocumentSyncKind.Incremental,
completionProvider: { resolveProvider: true },
codeLensProvider: { resolveProvider: true },
inlayHintProvider: {
resolveProvider: true,
documentSelector: ["js"],
},
codeActionProvider: true,
definitionProvider: true,
executeCommandProvider: {
Expand Down Expand Up @@ -98,6 +102,7 @@ connection.onDefinition((params) => service.definitions.onDefinition(params))
connection.onCodeAction((params) => service.codeActions.onCodeAction(params))
connection.onCodeLens((params) => service.codeLens.onCodeLens(params))
connection.onCodeLensResolve((codeLens) => service.codeLens.onCodeLensResolve(codeLens))
connection.onRequest("stimulus-lsp/inlayHints", (params) => service.inlayHints.onInlayHints(params))

connection.onExecuteCommand((params) => {
if (!params.arguments) return
Expand Down
3 changes: 3 additions & 0 deletions server/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CodeActions } from "./code_actions"
import { CodeLensProvider as CodeLens } from "./code_lens"

import { Project } from "stimulus-parser"
import { InlayHints } from "./inlay_hints"

export class Service {
connection: Connection
Expand All @@ -24,6 +25,7 @@ export class Service {
codeActions: CodeActions
project: Project
codeLens: CodeLens
inlayHints: InlayHints

constructor(connection: Connection, params: InitializeParams) {
this.connection = connection
Expand All @@ -36,6 +38,7 @@ export class Service {
this.definitions = new Definitions(this.documentService, this.stimulusDataProvider)
this.commands = new Commands(this.project, this.connection)
this.codeLens = new CodeLens(this.documentService, this.project)
this.inlayHints = new InlayHints(this.documentService, this.project)

this.htmlLanguageService = getLanguageService({
customDataProviders: [this.stimulusDataProvider],
Expand Down
5 changes: 5 additions & 0 deletions server/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==

"@types/vscode@^1.86.0":
version "1.86.0"
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.86.0.tgz#5d5f233137b27e51d7ad1462600005741296357a"
integrity sha512-DnIXf2ftWv+9LWOB5OJeIeaLigLHF7fdXF6atfc7X5g2w/wVZBgk0amP7b+ub5xAuW1q7qP5YcFvOcit/DtyCQ==

"@typescript-eslint/types@7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.0.2.tgz#b6edd108648028194eb213887d8d43ab5750351c"
Expand Down