diff --git a/extension/server/src/providers/completionItem.ts b/extension/server/src/providers/completionItem.ts index adf8ca59..578e32db 100644 --- a/extension/server/src/providers/completionItem.ts +++ b/extension/server/src/providers/completionItem.ts @@ -112,7 +112,7 @@ export default async function completionItemProvider(handler: CompletionParams): const item = CompletionItem.create(`${procedure.name}`); item.kind = CompletionItemKind.Function; item.insertTextFormat = InsertTextFormat.Snippet; - item.insertText = `${procedure.name}(${procedure.subItems.map((parm, index) => `\${${index + 1}:${parm.name}}`).join(`:`)})`; + item.insertText = procedure.name; item.detail = procedure.keywords.join(` `); item.documentation = procedure.description; items.push(item); diff --git a/extension/server/src/providers/linter/index.ts b/extension/server/src/providers/linter/index.ts index 9d7daa57..05bb245e 100644 --- a/extension/server/src/providers/linter/index.ts +++ b/extension/server/src/providers/linter/index.ts @@ -3,7 +3,7 @@ import { CodeAction, CodeActionKind, Diagnostic, DiagnosticSeverity, DidChangeWa import { TextDocument } from 'vscode-languageserver-textdocument'; import { URI } from 'vscode-uri'; import { documents, parser } from '..'; -import { IssueRange, Rules } from '../../../../../language/parserTypes'; +import * as parserTypes from '../../../../../language/parserTypes'; import Linter from '../../../../../language/linter'; import Cache from '../../../../../language/models/cache'; import codeActionsProvider from './codeActions'; @@ -12,12 +12,17 @@ import documentFormattingProvider from './documentFormatting'; import * as Project from "../project"; import { connection, getFileRequest, getWorkingDirectory, validateUri, watchedFilesChangeEvent } from '../../connection'; import { parseMemberUri } from '../../data'; +import Document from '../../../../../language/document'; +import { signatureHelpProvider } from './signatureHelp'; -export let jsonCache: { [uri: string]: string } = {}; +export let documentParseCache: { [uri: string]: Document } = {}; + +let jsonCache: { [uri: string]: string } = {}; export function initialise(connection: _Connection) { connection.onCodeAction(codeActionsProvider); connection.onDocumentFormatting(documentFormattingProvider); + connection.onSignatureHelp(signatureHelpProvider); // This only works for local workspace files watchedFilesChangeEvent.push((params: DidChangeWatchedFilesParams) => { @@ -75,7 +80,7 @@ export function initialise(connection: _Connection) { }) } -export function calculateOffset(document: TextDocument, error: IssueRange) { +export function calculateOffset(document: TextDocument, error: parserTypes.IssueRange) { const offset = error.offset; return Range.create( @@ -184,7 +189,7 @@ export async function refreshLinterDiagnostics(document: TextDocument, docs: Cac const indentDiags: Diagnostic[] = []; const generalDiags: Diagnostic[] = []; - const options: Rules = await getLintOptions(document.uri); + const options: parserTypes.Rules = await getLintOptions(document.uri); let detail; @@ -211,6 +216,8 @@ export async function refreshLinterDiagnostics(document: TextDocument, docs: Cac const indentErrors = detail.indentErrors; const errors = detail.errors; + documentParseCache[document.uri] = detail.doc; + if (indentErrors.length > 0) { indentErrors.forEach(error => { const range = Range.create(error.line, 0, error.line, error.currentIndent); @@ -244,7 +251,7 @@ export async function refreshLinterDiagnostics(document: TextDocument, docs: Cac } } -export function getActions(document: TextDocument, errors: IssueRange[]) { +export function getActions(document: TextDocument, errors: parserTypes.IssueRange[]) { let actions: CodeAction[] = []; // We need to move subroutine to the end and reverse the contents diff --git a/extension/server/src/providers/linter/signatureHelp.ts b/extension/server/src/providers/linter/signatureHelp.ts new file mode 100644 index 00000000..b59c43a9 --- /dev/null +++ b/extension/server/src/providers/linter/signatureHelp.ts @@ -0,0 +1,98 @@ +import { MarkedString, MarkupContent, MarkupKind, ParameterInformation, Range, SignatureHelp, SignatureHelpParams, SignatureHelpTriggerKind, SignatureInformation } from "vscode-languageserver"; +import { documents, parser } from ".."; +import { documentParseCache } from "."; +import Statement from "../../../../../language/statement"; +import Declaration from "../../../../../language/models/declaration"; + +export async function signatureHelpProvider(params: SignatureHelpParams): Promise { + const uri = params.textDocument.uri; + const document = documents.get(uri); + + if (document) { + const isFree = (document.getText(Range.create(0, 0, 0, 6)).toUpperCase() === `**FREE`); + + if (isFree) { + const offset = document?.offsetAt(params.position); + const parsedDocument = documentParseCache[uri]; + + if (parsedDocument) { + const statement = parsedDocument.getStatementByOffset(offset); + if (statement) { + const withBlocks = statement.asBlocks(); + const currentTokenIx = withBlocks.findIndex(token => offset >= token.range.start && offset <= token.range.end); + + // Check we're in a block type + if (currentTokenIx >= 0 && withBlocks[currentTokenIx].type === `block`) { + // Check the token before is a valid word + if (withBlocks[currentTokenIx - 1] && withBlocks[currentTokenIx - 1].type === `word`) { + const nameToken = withBlocks[currentTokenIx - 1]; + const parmBlock = withBlocks[currentTokenIx]; + + const nameValue = nameToken.value!.toUpperCase(); + const lineNumber = nameToken.range.line; + + if (document) { + const doc = await parser.getDocs(uri); + if (doc) { + + // If they're typing inside of a procedure, let's get the stuff from there too + const currentProcedure = doc.procedures.find((proc, index) => + lineNumber >= proc.range.start && + (lineNumber <= proc.range.end + 1 || index === doc.procedures.length - 1) + ); + + const possibleFunction: Declaration | undefined = [ + currentProcedure && currentProcedure.scope ? currentProcedure.scope.procedures.find(proc => proc.name.toUpperCase() === nameValue) : undefined, + doc.procedures.find(proc => proc.name.toUpperCase() === nameValue) + ].find(x => x); // find the first non-undefined item + + if (possibleFunction) { + const parms = Statement.getParameters(parmBlock.block!); + const currentParm = parms.findIndex(token => offset >= token.range.start && offset <= token.range.end); + const activeParameter = currentParm >= 0 ? currentParm : 0; + + let retrunValue = possibleFunction.keywords.filter(keyword => !keyword.startsWith(`EXTPROC`)); + if (retrunValue.length === 0) retrunValue = [`void`]; + + const parmsString = possibleFunction.subItems.map(x => x.name).join(` : `); + + let currentSig: SignatureInformation = { + label: `${possibleFunction.name}(${parmsString}): ${retrunValue.join(` `)}`, + activeParameter, + documentation: possibleFunction.description.trim().length > 0 ? + { + kind: MarkupKind.Markdown, + value: `---\n\n${possibleFunction.description}` + } : undefined, + + parameters: possibleFunction.subItems.map((parm, i): ParameterInformation => { + const docLines = [ + `\`${parm.name}: ${parm.keywords.join(` `)}\``, + parm.description.trim().length ? parm.description : undefined + ].filter(x => x).join(`\n\n`); + + return { + label: parm.name, + documentation: { + kind: MarkupKind.Markdown, + value: docLines + } + } + }) + }; + + return {signatures: [currentSig], activeSignature: 0, activeParameter}; + } + } + } + } + } + + return { signatures: [] }; + } + } + } + } + + return; +} \ No newline at end of file diff --git a/extension/server/src/server.ts b/extension/server/src/server.ts index f7809799..8d95d99d 100644 --- a/extension/server/src/server.ts +++ b/extension/server/src/server.ts @@ -67,7 +67,7 @@ connection.onInitialize((params: InitializeParams) => { result.capabilities.documentSymbolProvider = true; result.capabilities.definitionProvider = true; result.capabilities.completionProvider = { - triggerCharacters: [` `, `.`, `:`] + triggerCharacters: [` `, `.`, `:`, `(`] }; result.capabilities.hoverProvider = true; result.capabilities.referencesProvider = true; @@ -81,6 +81,9 @@ connection.onInitialize((params: InitializeParams) => { workDoneProgress: true }; } + result.capabilities.signatureHelpProvider = { + triggerCharacters: [`(`, `:`], + }; } if (hasWorkspaceFolderCapability) {