add some arg help
This commit is contained in:
parent
028ccf2bf9
commit
09d2420508
|
|
@ -2,6 +2,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||||
import { buildDiagnostics } from './diagnostics'
|
import { buildDiagnostics } from './diagnostics'
|
||||||
import { buildSemanticTokens, TOKEN_MODIFIERS, TOKEN_TYPES } from './semanticTokens'
|
import { buildSemanticTokens, TOKEN_MODIFIERS, TOKEN_TYPES } from './semanticTokens'
|
||||||
import { provideCompletions } from './completion/completionProvider'
|
import { provideCompletions } from './completion/completionProvider'
|
||||||
|
import { provideSignatureHelp } from './signatureHelp'
|
||||||
import { PRELUDE_NAMES } from './metadata/prelude-names'
|
import { PRELUDE_NAMES } from './metadata/prelude-names'
|
||||||
import { parser } from '../../../src/parser/shrimp'
|
import { parser } from '../../../src/parser/shrimp'
|
||||||
import { setGlobals } from '../../../src/parser/tokenizer'
|
import { setGlobals } from '../../../src/parser/tokenizer'
|
||||||
|
|
@ -29,6 +30,7 @@ connection.onInitialize(handleInitialize)
|
||||||
connection.languages.semanticTokens.on(handleSemanticTokens)
|
connection.languages.semanticTokens.on(handleSemanticTokens)
|
||||||
documents.onDidChangeContent(handleDocumentChange)
|
documents.onDidChangeContent(handleDocumentChange)
|
||||||
connection.onCompletion(handleCompletion)
|
connection.onCompletion(handleCompletion)
|
||||||
|
connection.onSignatureHelp(handleSignatureHelp)
|
||||||
|
|
||||||
// Debug commands
|
// Debug commands
|
||||||
connection.onRequest('shrimp/parseTree', handleParseTree)
|
connection.onRequest('shrimp/parseTree', handleParseTree)
|
||||||
|
|
@ -49,6 +51,9 @@ function handleInitialize(): InitializeResult {
|
||||||
completionProvider: {
|
completionProvider: {
|
||||||
triggerCharacters: ['.'],
|
triggerCharacters: ['.'],
|
||||||
},
|
},
|
||||||
|
signatureHelpProvider: {
|
||||||
|
triggerCharacters: [' '],
|
||||||
|
},
|
||||||
semanticTokensProvider: {
|
semanticTokensProvider: {
|
||||||
legend: {
|
legend: {
|
||||||
tokenTypes: TOKEN_TYPES,
|
tokenTypes: TOKEN_TYPES,
|
||||||
|
|
@ -113,6 +118,12 @@ function handleCompletion(params: any) {
|
||||||
return [...keywordCompletions, ...preludeCompletions]
|
return [...keywordCompletions, ...preludeCompletions]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSignatureHelp(params: any) {
|
||||||
|
const document = documents.get(params.textDocument.uri)
|
||||||
|
if (!document) return
|
||||||
|
return provideSignatureHelp(document, params.position)
|
||||||
|
}
|
||||||
|
|
||||||
function handleParseTree(params: { uri: string }) {
|
function handleParseTree(params: { uri: string }) {
|
||||||
connection.console.log(`🦐 Parse tree requested for: ${params.uri}`)
|
connection.console.log(`🦐 Parse tree requested for: ${params.uri}`)
|
||||||
const document = documents.get(params.uri)
|
const document = documents.get(params.uri)
|
||||||
|
|
|
||||||
105
vscode-extension/server/src/signatureHelp.ts
Normal file
105
vscode-extension/server/src/signatureHelp.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { SignatureHelp, SignatureInformation, ParameterInformation } from 'vscode-languageserver/node'
|
||||||
|
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||||
|
import { Tree, SyntaxNode } from '@lezer/common'
|
||||||
|
import { parser } from '../../../src/parser/shrimp'
|
||||||
|
import { completions } from './metadata/prelude-completions'
|
||||||
|
|
||||||
|
export const provideSignatureHelp = (
|
||||||
|
document: TextDocument,
|
||||||
|
position: { line: number; character: number }
|
||||||
|
): SignatureHelp | undefined => {
|
||||||
|
const text = document.getText()
|
||||||
|
const tree = parser.parse(text)
|
||||||
|
const cursorPos = document.offsetAt(position)
|
||||||
|
|
||||||
|
const context = findCallContext(tree, cursorPos, text)
|
||||||
|
if (!context) return
|
||||||
|
|
||||||
|
const params = lookupFunctionParams(context.funcName)
|
||||||
|
if (!params) return
|
||||||
|
|
||||||
|
return {
|
||||||
|
signatures: [buildSignature(context.funcName, params)],
|
||||||
|
activeParameter: Math.min(context.argCount, params.length - 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const findCallContext = (tree: Tree, cursorPos: number, text: string) => {
|
||||||
|
const findBestCall = (node: SyntaxNode): SyntaxNode | undefined => {
|
||||||
|
let result: SyntaxNode | undefined
|
||||||
|
|
||||||
|
const isCall = node.name === 'FunctionCall' || node.name === 'FunctionCallOrIdentifier'
|
||||||
|
|
||||||
|
// Call ends just before cursor (within 5 chars)
|
||||||
|
if (isCall && node.to <= cursorPos && cursorPos <= node.to + 5) {
|
||||||
|
result = node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor is inside the call's span
|
||||||
|
if (isCall && node.from < cursorPos && cursorPos < node.to) {
|
||||||
|
result = node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recurse - prefer smaller spans (more specific)
|
||||||
|
let child = node.firstChild
|
||||||
|
while (child) {
|
||||||
|
const found = findBestCall(child)
|
||||||
|
if (found) {
|
||||||
|
const foundSpan = found.to - found.from
|
||||||
|
const resultSpan = result ? result.to - result.from : Infinity
|
||||||
|
if (foundSpan < resultSpan) {
|
||||||
|
result = found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child = child.nextSibling
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const call = findBestCall(tree.topNode)
|
||||||
|
if (!call) return
|
||||||
|
|
||||||
|
// Count args before cursor
|
||||||
|
let argCount = 0
|
||||||
|
let child = call.firstChild
|
||||||
|
while (child) {
|
||||||
|
if ((child.name === 'PositionalArg' || child.name === 'NamedArg') && child.to <= cursorPos) {
|
||||||
|
argCount++
|
||||||
|
}
|
||||||
|
child = child.nextSibling
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract function name
|
||||||
|
const firstChild = call.firstChild
|
||||||
|
if (!firstChild) return
|
||||||
|
|
||||||
|
let funcName: string | undefined
|
||||||
|
if (firstChild.name === 'DotGet') {
|
||||||
|
funcName = text.slice(firstChild.from, firstChild.to)
|
||||||
|
} else if (firstChild.name === 'Identifier') {
|
||||||
|
funcName = text.slice(firstChild.from, firstChild.to)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!funcName) return
|
||||||
|
|
||||||
|
return { funcName, argCount }
|
||||||
|
}
|
||||||
|
|
||||||
|
const lookupFunctionParams = (funcName: string): string[] | undefined => {
|
||||||
|
// Handle module functions: "list.map" → modules.list.map
|
||||||
|
if (funcName.includes('.')) {
|
||||||
|
const [moduleName, methodName] = funcName.split('.')
|
||||||
|
const module = completions.modules[moduleName as keyof typeof completions.modules]
|
||||||
|
const method = module?.[methodName as keyof typeof module]
|
||||||
|
return method?.params as string[] | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle top-level prelude functions (print, range, etc.)
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildSignature = (funcName: string, params: string[]): SignatureInformation => {
|
||||||
|
const label = `${funcName}(${params.join(', ')})`
|
||||||
|
const parameters: ParameterInformation[] = params.map(p => ({ label: p }))
|
||||||
|
return { label, parameters }
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user