diff --git a/spin2/CHANGELOG.md b/spin2/CHANGELOG.md index ee56da3..677704a 100644 --- a/spin2/CHANGELOG.md +++ b/spin2/CHANGELOG.md @@ -20,11 +20,14 @@ Possible next additions: - Add new-file templates as Snippets - Add additional Snippets as the community identifies them +## [2.2.0] 2023-10-28 + +- Awaken **Show Definitions of a Symbol feature** supporting go-to definition. (returns one or more matching symbols found in current open find and included objects) + ## [2.1.0] 2023-10-27 Formal release of Language-server-based P1 and P2 Spin Extension for VScode - - Files included by current file are parsed and references to the included objects are validated - Documentation from the included object files is shown for Hover Text and Signature help - Live parsing on file change allowing changes in one editor window to affect another editor window (e.g., You have a toplevel spin file open and and object spin file open in 2nd window. Changes in object file can immediately affect the toplevel file.) @@ -32,7 +35,6 @@ Formal release of Language-server-based P1 and P2 Spin Extension for VScode - What a file is parsed and errors are found the file entry in the left panel file browser turns light red to highlight that file contains errors - Many improvements in parsing / highlighting for both P1 and P2 - ## [2.0.0 - 2.0.4] 2023 Oct 22-26 Convert to Spin and Spin2 Language Server as separate Process (P1 and P2) diff --git a/spin2/README.md b/spin2/README.md index 57495a6..1156184 100644 --- a/spin2/README.md +++ b/spin2/README.md @@ -10,7 +10,7 @@ This Extension is continually in development. Things may, occasionally, not work This extension provides support for P1 (spin and pasm) along with P2 (Spin2 and Pasm2), the primary languages for programming P1 [Parallax Propeller 1 or P8X32A](https://www.parallax.com/propeller-1/) and the P2 [Parallax Propeller2 or P2X8C4M64P](https://propeller.parallax.com/p2.html) -We've moved to a **Language Server based extension** so that we can awaken **multi-file behaviors** such as show help from included file in top-level file when hovering or showing signature help. This also applies to upoming features such as go to definition. +We've moved to a **Language Server based extension** so that we can awaken **multi-file behaviors** such as show help from included file in top-level file when hovering or showing signature help. This also applies to upoming features such as go to definition. All features provided by this extension support both the Parallax Propeller 1 and Propeller 2 languages: Spin and Pasm. @@ -79,6 +79,13 @@ Help With Method Signatures displays information about the method that is being - If your own methods are not yet documented, the this signature help still supports entry of the parameter values as well as reminds you how to add your own documentation for your PUB and PRI methods. - When the method being entered is from an included object the help text is brought in from the external included object. +## Feature: Show Definitions of a Symbol + +Allow the user to see/go to the definition of variables/methods right where the variables/methods are being used. + +- Enables right-mouse commands "Go to Definition" and "Peek -> Peek Definition" +- In spin this works for method names, global variables, parameters, return values, method local variables and pasm global labels. + ## Feature: Generate "Object public interface" documentation Upon pressing Ctrl+Alt+d (control alt document) the editor will now generate a `{filename}.txt` document file (for your `{filename}.spin2` or `{filename}.spin` file) and open it up to the right side of your editor window. The generator extracts all PUB methods and their doc-comments along with file-top and file-bottom doc-comments. diff --git a/spin2/package.json b/spin2/package.json index fce4037..ea1e90e 100644 --- a/spin2/package.json +++ b/spin2/package.json @@ -4,7 +4,7 @@ "description": "P1 and P2 Spin/Pasm Syntax/Semantic Highlighting w/Code Outline, Object Outline and Custom tabbing support", "author": "IronSheep", "license": "MIT", - "version": "2.1.0", + "version": "2.2.0", "repository": { "type": "git", "url": "https://github.com/ironsheep/P2-vscode-langserv-extension" diff --git a/spin2/server/src/DocumentProcessor.ts b/spin2/server/src/DocumentProcessor.ts index 7af7b40..6893e88 100644 --- a/spin2/server/src/DocumentProcessor.ts +++ b/spin2/server/src/DocumentProcessor.ts @@ -65,8 +65,8 @@ export class ProcessedDocument { } } -export type ProcessedDocumentByURI = Map; -export type DocumentFindingsByURI = Map; +export type ProcessedDocumentByFSpec = Map; +export type DocumentFindingsByFSpec = Map; export type TopDocsByFSpec = Map; // ---------------------------------------------------------------------------- @@ -203,7 +203,7 @@ export default class DocumentProcessor { // let tmpFindingsForDocument: DocumentFindings | undefined = this.ctx.findingsByFSpec.get(docFSpec); if (!tmpFindingsForDocument) { - tmpFindingsForDocument = new DocumentFindings(); + tmpFindingsForDocument = new DocumentFindings(document.uri); tmpFindingsForDocument.setFilename(docFSpec); this.ctx.findingsByFSpec.set(docFSpec, tmpFindingsForDocument); this.ctx.logger.log(`TRC: ADD Findings: ${tmpFindingsForDocument.instanceName()}`); diff --git a/spin2/server/src/context.ts b/spin2/server/src/context.ts index d2c71a6..a86e651 100644 --- a/spin2/server/src/context.ts +++ b/spin2/server/src/context.ts @@ -2,7 +2,7 @@ // src/context.ts import * as lsp from "vscode-languageserver"; -import { ProcessedDocumentByURI, DocumentFindingsByURI, TopDocsByFSpec } from "./DocumentProcessor"; +import { ProcessedDocumentByFSpec, DocumentFindingsByFSpec, TopDocsByFSpec } from "./DocumentProcessor"; //import Parser from "web-tree-sitter"; //import path from "path"; @@ -18,8 +18,8 @@ export class ServerBehaviorConfiguration { export interface Context { topDocsByFSpec: TopDocsByFSpec; - docsByFSpec: ProcessedDocumentByURI; - findingsByFSpec: DocumentFindingsByURI; + docsByFSpec: ProcessedDocumentByFSpec; + findingsByFSpec: DocumentFindingsByFSpec; workspaceFolders: lsp.WorkspaceFolder[]; language: string; logger: lsp.Logger; diff --git a/spin2/server/src/parser/spin.objectReferenceParser.ts b/spin2/server/src/parser/spin.objectReferenceParser.ts index 23c8d36..ed2d46a 100644 --- a/spin2/server/src/parser/spin.objectReferenceParser.ts +++ b/spin2/server/src/parser/spin.objectReferenceParser.ts @@ -301,7 +301,7 @@ export class Spin2ObjectReferenceParser { } if (enumConstant.charAt(0).match(/[a-zA-Z_]/)) { this._logCON(" -- GLBL enumConstant=[" + enumConstant + "]"); - this.semanticFindings.setGlobalToken(enumConstant, new RememberedToken("enumMember", ["readonly"]), lineNbr, undefined); + this.semanticFindings.setGlobalToken(enumConstant, new RememberedToken("enumMember", lineNbr - 1, ["readonly"]), undefined); } } } diff --git a/spin2/server/src/parser/spin.semantic.findings.ts b/spin2/server/src/parser/spin.semantic.findings.ts index 5420e18..15f018e 100644 --- a/spin2/server/src/parser/spin.semantic.findings.ts +++ b/spin2/server/src/parser/spin.semantic.findings.ts @@ -5,6 +5,7 @@ import { Range, DiagnosticSeverity, SymbolKind, Diagnostic } from "vscode-langua import { displayEnumByTypeName } from "./spin2.utils"; import { eDebugDisplayType } from "./spin.common"; import { Context } from "../context"; +import { Position } from "vscode-languageserver-textdocument"; // ============================================================================ // this file contains objects we use in tracking symbol use and declaration @@ -36,6 +37,12 @@ enum eCommentFilter { allComments, } +export interface ILocationOfToken { + uri: string; + objectName: string; + position: Position; // if more detail desired in future capture and return token offset into line! +} + export interface IBlockSpan { startLineIdx: number; endLineIdx: number; @@ -137,8 +144,12 @@ export class DocumentFindings { // tracking includes private objectFilenameByInstanceName = new Map(); private ctx: Context | undefined; + private docUri: string = "--uri-not-set--"; - public constructor() { + public constructor(documentUri: string | undefined = undefined) { + if (documentUri) { + this.docUri = documentUri; + } if (this.findingsLogEnabled) { if (this.bLogStarted == false) { this.bLogStarted = true; @@ -158,6 +169,11 @@ export class DocumentFindings { this.methodLocalPasmTokens = new NameScopedTokenSet("methPasmTOK"); } + public get uri(): string { + // property: URI for doc of these findings + return this.docUri; + } + public setFilename(filespec: string): void { // append filespec to our instance number const orignalId: string = this.instanceId; @@ -380,6 +396,68 @@ export class DocumentFindings { return symbolsInNamespace; } + public getNamespaces(): string[] { + // return list of object namespaces found in toplevel + const nameSpaceSet: string[] = Array.from(this.objectParseResultByObjectName.keys()); + return nameSpaceSet; + } + + public locationsOfToken(tokenName: string): ILocationOfToken[] { + const desiredLocations: ILocationOfToken[] = []; + this.appendLocationsOfToken(tokenName, desiredLocations, "top"); + this._logMessage(` -- locationsOfToken() id=[${this.instanceId}] returns ${desiredLocations.length} tokens`); + return desiredLocations; + } + + public appendLocationsOfToken(tokenName: string, locationsSoFar: ILocationOfToken[], objectName: string) { + let referenceDetails: RememberedToken | undefined = undefined; + const desiredTokenKey: string = tokenName.toLowerCase(); + let findCount: number = 0; + // get global token from this objects + if (this.isGlobalToken(tokenName)) { + referenceDetails = this.getGlobalToken(tokenName); + if (referenceDetails) { + const tokenPosition: Position = { line: referenceDetails.lineIndex, character: 0 }; + const tokenRef: ILocationOfToken = { uri: this.uri, objectName: objectName, position: tokenPosition }; + locationsSoFar.push(tokenRef); + findCount++; + this._logMessage(` -- appLoc-Token FOUND global token=[${tokenName}]`); + } else { + this._logMessage(` -- appLoc-Token global token=[${tokenName}] has NO lineNbr info!`); + } + } + if (this.isLocalToken(tokenName)) { + // get local tokens from this objects + const referenceSet: RememberedToken[] = this.getLocalTokens(tokenName); + for (let index = 0; index < referenceSet.length; index++) { + referenceDetails = referenceSet[index]; + if (referenceDetails) { + const tokenPosition: Position = { line: referenceDetails.lineIndex, character: 0 }; + const tokenRef: ILocationOfToken = { uri: this.uri, objectName: objectName, position: tokenPosition }; + locationsSoFar.push(tokenRef); + findCount++; + this._logMessage(` -- appLoc-Token FOUND local token=[${tokenName}]`); + } else { + this._logMessage(` -- appLoc-Token local token=[${tokenName}] has NO lineNbr info!`); + } + } + } + const referencedObjects: string[] = this.getNamespaces(); + // get global/local tokens from all included objects + for (let index = 0; index < referencedObjects.length; index++) { + const nameSpace = referencedObjects[index]; + const symbolsFound: DocumentFindings | undefined = this.getFindingsForNamespace(nameSpace); + + if (symbolsFound) { + if (this.ctx) { + symbolsFound.enableLogging(this.ctx, this.findingsLogEnabled); + } + symbolsFound.appendLocationsOfToken(tokenName, locationsSoFar, nameSpace); + } + } + this._logMessage(` -- appendLocationsOfToken() id=[${this.instanceId}] adds ${findCount} tokens`); + } + // ------------------------------------------------------------------------------------- // TRACK ranges of CON/PUB/PRI/VAR/DAT/OBJ blocks within file // @@ -657,7 +735,7 @@ export class DocumentFindings { const displayInfo: IDebugDisplayInfo = this.getDebugDisplayInfoForUserName(tokenName); if (displayInfo.eDisplayType != eDebugDisplayType.Unknown) { // we have a debug display type! - findings.token = new RememberedToken("debugDisplay", [displayInfo.displayTypeString]); + findings.token = new RememberedToken("debugDisplay", displayInfo.lineNbr - 1, [displayInfo.displayTypeString]); findings.scope = "Global"; findings.tokenRawInterp = "Global: " + this._rememberdTokenString(tokenName, findings.token); const termType: string = displayInfo.displayTypeString.toUpperCase(); @@ -935,12 +1013,12 @@ export class DocumentFindings { this._logMessage(` `); } - public setGlobalToken(tokenName: string, token: RememberedToken, declarationLineNumber: number, declarationComment: string | undefined, reference?: string | undefined): void { + public setGlobalToken(tokenName: string, token: RememberedToken, declarationComment: string | undefined, reference?: string | undefined): void { if (!this.isGlobalToken(tokenName)) { - this._logMessage(" -- NEW-gloTOK " + this._rememberdTokenString(tokenName, token) + `, ln#${declarationLineNumber}, cmt=[${declarationComment}], ref=[${reference}]`); + this._logMessage(" -- NEW-gloTOK " + this._rememberdTokenString(tokenName, token) + `, ln#${token.lineIndex + 1}, cmt=[${declarationComment}], ref=[${reference}]`); this.globalTokens.setToken(tokenName, token); // and remember declataion line# for this token - const newDescription: RememberedTokenDeclarationInfo = new RememberedTokenDeclarationInfo(declarationLineNumber - 1, declarationComment, reference); + const newDescription: RememberedTokenDeclarationInfo = new RememberedTokenDeclarationInfo(token.lineIndex, declarationComment, reference); const desiredTokenKey: string = tokenName.toLowerCase(); this.declarationInfoByGlobalTokenName.set(desiredTokenKey, newDescription); } @@ -952,12 +1030,27 @@ export class DocumentFindings { // let's never return a declaration modifier! (somehow declaration creeps in to our list!??) //let modifiersNoDecl: string[] = this._modifiersWithout(desiredToken.modifiers, "declaration"); let modifiersNoDecl: string[] = desiredToken.modifiersWithout("declaration"); - desiredToken = new RememberedToken(desiredToken.type, modifiersNoDecl); + desiredToken = new RememberedToken(desiredToken.type, desiredToken.lineIndex, modifiersNoDecl); this._logMessage(" -- FND-gloTOK " + this._rememberdTokenString(tokenName, desiredToken)); } return desiredToken; } + public getLocalTokens(tokenName: string): RememberedToken[] { + const desiredTokens: RememberedToken[] = []; + if (this.isLocalToken(tokenName)) { + const methodNameKeys: string[] = this.methodLocalTokens.keys(); + for (let index = 0; index < methodNameKeys.length; index++) { + const methodName = methodNameKeys[index]; + const tokenForMethod: RememberedToken | undefined = this.getLocalTokenForMethod(tokenName, methodName); + if (tokenForMethod) { + desiredTokens.push(tokenForMethod); + } + } + } + return desiredTokens; + } + public isLocalToken(tokenName: string): boolean { const foundStatus: boolean = this.methodLocalTokens.hasToken(tokenName); this._logMessage(` -- IS-locTOK [${tokenName}] says ${foundStatus}`); @@ -969,13 +1062,13 @@ export class DocumentFindings { return foundStatus; } - public setLocalTokenForMethod(methodName: string, tokenName: string, token: RememberedToken, declarationLineNumber: number, declarationComment: string | undefined): void { + public setLocalTokenForMethod(methodName: string, tokenName: string, token: RememberedToken, declarationComment: string | undefined): void { if (!this.isLocalTokenForMethod(methodName, tokenName)) { - this._logMessage(` -- NEW-locTOK ln#${declarationLineNumber} method=[${methodName}], ` + this._rememberdTokenString(tokenName, token) + `, cmt=[${declarationComment}]`); + this._logMessage(` -- NEW-locTOK ln#${token.lineIndex + 1} method=[${methodName}], ` + this._rememberdTokenString(tokenName, token) + `, cmt=[${declarationComment}]`); this.methodLocalTokens.setTokenForMethod(methodName, tokenName, token); // and remember declataion line# for this token const desiredTokenKey: string = tokenName.toLowerCase(); - this.declarationInfoByLocalTokenName.set(desiredTokenKey, new RememberedTokenDeclarationInfo(declarationLineNumber - 1, declarationComment)); + this.declarationInfoByLocalTokenName.set(desiredTokenKey, new RememberedTokenDeclarationInfo(token.lineIndex, declarationComment)); } } @@ -996,6 +1089,11 @@ export class DocumentFindings { return desiredToken; } + private getLocalTokenForMethod(tokenName: string, methodName: string): RememberedToken | undefined { + const desiredToken: RememberedToken | undefined = this.methodLocalTokens.getTokenForMethod(methodName, tokenName); + return desiredToken; + } + public startMethod(methodName: string, lineNbr: number): void { // starting a new method remember the name and assoc the line number if (this.currMethodName) { @@ -1058,7 +1156,7 @@ export class DocumentFindings { return foundStatus; } - public setLocalPAsmTokenForMethod(methodName: string, tokenName: string, token: RememberedToken, declarationLineNumber: number, declarationComment: string | undefined): void { + public setLocalPAsmTokenForMethod(methodName: string, tokenName: string, token: RememberedToken, declarationComment: string | undefined): void { if (this.hasLocalPAsmTokenForMethod(methodName, tokenName)) { // WARNING attempt to set again } else { @@ -1066,7 +1164,7 @@ export class DocumentFindings { this.methodLocalPasmTokens.setTokenForMethod(methodName, tokenName, token); // and remember declataion line# for this token const desiredTokenKey: string = tokenName.toLowerCase(); - this.declarationInfoByLocalTokenName.set(desiredTokenKey, new RememberedTokenDeclarationInfo(declarationLineNumber - 1, declarationComment)); + this.declarationInfoByLocalTokenName.set(desiredTokenKey, new RememberedTokenDeclarationInfo(token.lineIndex, declarationComment)); const newToken = this.methodLocalPasmTokens.getTokenForMethod(methodName, tokenName); if (newToken) { this._logMessage(" -- NEW-lpTOK method=" + methodName + ": " + this._rememberdTokenString(tokenName, newToken)); @@ -1283,7 +1381,7 @@ export class TokenSet { // let's never return a declaration modifier! (somehow "declaration" creeps in to our list!??) //let modifiersNoDecl: string[] = this._modifiersWithout(desiredToken.modifiers, "declaration"); let modifiersNoDecl: string[] = desiredToken.modifiersWithout("declaration"); - desiredToken = new RememberedToken(desiredToken.type, modifiersNoDecl); + desiredToken = new RememberedToken(desiredToken.type, desiredToken._lineIdx, modifiersNoDecl); } return desiredToken; } @@ -1329,8 +1427,8 @@ export class NameScopedTokenSet { return Array.from(this.methodScopedTokenSetByMethodKey.entries()); } - public keys() { - return this.methodScopedTokenSetByMethodKey.keys(); + public keys(): string[] { + return Array.from(this.methodScopedTokenSetByMethodKey.keys()); } public clear(): void { @@ -1488,19 +1586,27 @@ export class NameScopedTokenSet { export class RememberedToken { _type: string; _modifiers: string[] = []; - constructor(type: string, modifiers: string[] | undefined) { + _lineIdx: number; + constructor(type: string, lineIdx: number, modifiers: string[] | undefined) { this._type = type; + this._lineIdx = lineIdx; if (modifiers != undefined) { this._modifiers = modifiers; } } + get type(): string { return this._type; } + get modifiers(): string[] { return this._modifiers; } + get lineIndex(): number { + return this._lineIdx; + } + public isPublic(): boolean { // is symbol from CON section or is PUB method? let publicStatus: boolean = false; diff --git a/spin2/server/src/parser/spin1.documentSemanticParser.ts b/spin2/server/src/parser/spin1.documentSemanticParser.ts index f379458..3f0d08d 100644 --- a/spin2/server/src/parser/spin1.documentSemanticParser.ts +++ b/spin2/server/src/parser/spin1.documentSemanticParser.ts @@ -746,7 +746,7 @@ export class Spin1DocumentSemanticParser { if (symbolName != undefined && directive.toLowerCase() == "#define") { this._logPreProc(" -- new PreProc Symbol=[" + symbolName + "]"); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(symbolName, new RememberedToken("variable", ["readonly"]), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(symbolName, new RememberedToken("variable", lineNbr - 1, ["readonly"]), this._declarationComment()); } } } @@ -799,7 +799,7 @@ export class Spin1DocumentSemanticParser { this._logCON(" -- GLBL GetCONDecl newName=[" + newName + "]"); // remember this object name so we can annotate a call to it this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(newName, new RememberedToken("variable", ["readonly"]), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(newName, new RememberedToken("variable", lineNbr - 1, ["readonly"]), this._declarationComment()); } } } @@ -818,7 +818,7 @@ export class Spin1DocumentSemanticParser { if (enumConstant.charAt(0).match(/[a-zA-Z_]/)) { this._logCON(" -- GLBL enumConstant=[" + enumConstant + "]"); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(enumConstant, new RememberedToken("enumMember", ["readonly"]), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(enumConstant, new RememberedToken("enumMember", lineNbr - 1, ["readonly"]), this._declarationComment()); } } } @@ -882,7 +882,7 @@ export class Spin1DocumentSemanticParser { this._logDAT(" -- GLBL gddcl fileName=[" + fileName + "]"); this._ensureDataFileExists(fileName, lineNbr - 1, line, startingOffset); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(newName, new RememberedToken(nameType, labelModifiers), lineNbr, this._declarationComment(), fileName); + this.semanticFindings.setGlobalToken(newName, new RememberedToken(nameType, lineNbr - 1, labelModifiers), this._declarationComment(), fileName); } else if (notOKSpin2Word) { this.semanticFindings.pushDiagnosticMessage(lineNbr - 1, nameOffset, nameOffset + newName.length, eSeverity.Information, `Possible use of P2 Spin reserved word [${newName}]`); } @@ -916,9 +916,9 @@ export class Spin1DocumentSemanticParser { // record expectation of object public interface this.semanticFindings.recordDeclarationLine(line, lineNbr); if (bISMethod) { - this.semanticFindings.setGlobalToken(objRef, new RememberedToken("method", []), lineNbr, this._declarationComment(), objName); + this.semanticFindings.setGlobalToken(objRef, new RememberedToken("method", lineNbr - 1, []), this._declarationComment(), objName); } else { - this.semanticFindings.setGlobalToken(objRef, new RememberedToken("variable", ["readonly"]), lineNbr, this._declarationComment(), objName); + this.semanticFindings.setGlobalToken(objRef, new RememberedToken("variable", lineNbr - 1, ["readonly"]), this._declarationComment(), objName); } } } @@ -963,7 +963,7 @@ export class Spin1DocumentSemanticParser { this._logPASM(" -- DAT PASM label-ref fileName=[" + fileName + "]"); this._ensureDataFileExists(fileName, lineNbr - 1, line, startingOffset); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(labelName, new RememberedToken(labelType, labelModifiers), lineNbr, this._declarationComment(), fileName); + this.semanticFindings.setGlobalToken(labelName, new RememberedToken(labelType, lineNbr - 1, labelModifiers), this._declarationComment(), fileName); } } } @@ -1026,7 +1026,7 @@ export class Spin1DocumentSemanticParser { const filenamePart = lineParts[1].trim().replace(/[\"]/g, ""); this._logOBJ(` -- GLBL GetOBJDecl newFileName=[${filenamePart}]`); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(instanceNamePart, new RememberedToken("namespace", []), lineNbr, this._declarationComment(), filenamePart); // pass filename, too + this.semanticFindings.setGlobalToken(instanceNamePart, new RememberedToken("namespace", lineNbr - 1, []), this._declarationComment(), filenamePart); // pass filename, too this.semanticFindings.recordObjectImport(instanceNamePart, filenamePart); this._ensureObjectFileExists(filenamePart, lineNbr - 1, line, startingOffset); } else if (remainingNonCommentLineStr.length > 0 && !remainingNonCommentLineStr.includes(":")) { @@ -1081,7 +1081,7 @@ export class Spin1DocumentSemanticParser { // record ACTUAL object public/private interface // FIXME: post non-blank line after this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(methodName, new RememberedToken("method", refModifiers), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(methodName, new RememberedToken("method", lineNbr - 1, refModifiers), this._declarationComment()); // reset our list of local variables this.semanticFindings.clearLocalPAsmTokensForMethod(methodName); this._logSPIN(" -- _getPUB_PRI_Name() exit"); @@ -1116,7 +1116,7 @@ export class Spin1DocumentSemanticParser { if (newName.charAt(0).match(/[a-zA-Z_]/)) { this._logVAR(" -- GLBL GetVarDecl newName=[" + newName + "]"); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(newName, new RememberedToken("variable", ["instance"]), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(newName, new RememberedToken("variable", lineNbr - 1, ["instance"]), this._declarationComment()); } } } else if (!hasGoodType && lineParts.length > 0) { @@ -1125,7 +1125,7 @@ export class Spin1DocumentSemanticParser { if (longVarName.charAt(0).match(/[a-zA-Z_]/)) { this._logVAR(" -- GLBL GetVarDecl newName=[" + longVarName + "]"); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(longVarName, new RememberedToken("variable", ["instance"]), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(longVarName, new RememberedToken("variable", lineNbr - 1, ["instance"]), this._declarationComment()); } } } @@ -1845,7 +1845,7 @@ export class Spin1DocumentSemanticParser { ptTokenModifiers: ["declaration", "readonly", "local"], }); // remember so we can ID references - this.semanticFindings.setLocalTokenForMethod(methodName, paramName, new RememberedToken("parameter", ["readonly", "local"]), lineNbr, this._declarationComment()); // TOKEN SET in _report() + this.semanticFindings.setLocalTokenForMethod(methodName, paramName, new RememberedToken("parameter", lineNbr - 1, ["readonly", "local"]), this._declarationComment()); // TOKEN SET in _report() currentOffset = nameOffset + paramName.length; } } @@ -1887,7 +1887,7 @@ export class Spin1DocumentSemanticParser { ptTokenModifiers: ["declaration", "local"], }); // remember so we can ID references - this.semanticFindings.setLocalTokenForMethod(methodName, returnValueName, new RememberedToken("returnValue", ["local"]), lineNbr, this._declarationComment()); // TOKEN SET in _report() + this.semanticFindings.setLocalTokenForMethod(methodName, returnValueName, new RememberedToken("returnValue", lineNbr - 1, ["local"]), this._declarationComment()); // TOKEN SET in _report() currentOffset = nameOffset + returnValueName.length; } } @@ -1990,7 +1990,7 @@ export class Spin1DocumentSemanticParser { ptTokenModifiers: ["declaration", "local"], }); // remember so we can ID references - this.semanticFindings.setLocalTokenForMethod(methodName, localName, new RememberedToken("variable", ["local"]), lineNbr, this._declarationComment()); // TOKEN SET in _report() + this.semanticFindings.setLocalTokenForMethod(methodName, localName, new RememberedToken("variable", lineNbr - 1, ["local"]), this._declarationComment()); // TOKEN SET in _report() } else { // have modifier! if (this.parseUtils.isStorageType(localName)) { diff --git a/spin2/server/src/parser/spin2.documentSemanticParser.ts b/spin2/server/src/parser/spin2.documentSemanticParser.ts index 851e749..8df1844 100644 --- a/spin2/server/src/parser/spin2.documentSemanticParser.ts +++ b/spin2/server/src/parser/spin2.documentSemanticParser.ts @@ -979,7 +979,7 @@ export class Spin2DocumentSemanticParser { if (symbolName != undefined && directive.toLowerCase() == "#define") { this._logPreProc(" -- new PreProc Symbol=[" + symbolName + "]"); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(symbolName, new RememberedToken("variable", ["readonly"]), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(symbolName, new RememberedToken("variable", lineNbr - 1, ["readonly"]), this._declarationComment()); } } } @@ -1029,7 +1029,7 @@ export class Spin2DocumentSemanticParser { this._logCON(" -- GLBL GetCONDecl newName=[" + newName + "]"); // remember this object name so we can annotate a call to it this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(newName, new RememberedToken("variable", ["readonly"]), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(newName, new RememberedToken("variable", lineNbr - 1, ["readonly"]), this._declarationComment()); } } } @@ -1059,7 +1059,7 @@ export class Spin2DocumentSemanticParser { if (enumConstant.charAt(0).match(/[a-zA-Z_]/)) { this._logCON(` -- C GLBL enumConstant=[${enumConstant}]`); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(enumConstant, new RememberedToken("enumMember", ["readonly"]), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(enumConstant, new RememberedToken("enumMember", lineNbr - 1, ["readonly"]), this._declarationComment()); } } } @@ -1132,7 +1132,7 @@ export class Spin2DocumentSemanticParser { this._ensureDataFileExists(fileName, lineNbr - 1, line, startingOffset); this._logDAT(" -- GetDatDecl fileName=[" + fileName + "]"); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(newName, new RememberedToken(nameType, labelModifiers), lineNbr, this._declarationComment(), fileName); + this.semanticFindings.setGlobalToken(newName, new RememberedToken(nameType, lineNbr - 1, labelModifiers), this._declarationComment(), fileName); } } } @@ -1169,7 +1169,7 @@ export class Spin2DocumentSemanticParser { this._logDAT(" -- DAT PASM GLBL fileName=[" + fileName + "]"); this._ensureDataFileExists(fileName, lineNbr - 1, line, startingOffset); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(labelName, new RememberedToken(labelType, labelModifiers), lineNbr, this._declarationComment(), fileName); + this.semanticFindings.setGlobalToken(labelName, new RememberedToken(labelType, lineNbr - 1, labelModifiers), this._declarationComment(), fileName); } } } @@ -1235,7 +1235,7 @@ export class Spin2DocumentSemanticParser { const filenamePart = lineParts[1].trim().replace(/[\"]/g, ""); this._logOBJ(` -- GLBL GetOBJDecl newFileName=[${filenamePart}]`); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(instanceNamePart, new RememberedToken("namespace", []), lineNbr, this._declarationComment(), filenamePart); // pass filename, too + this.semanticFindings.setGlobalToken(instanceNamePart, new RememberedToken("namespace", lineNbr - 1, []), this._declarationComment(), filenamePart); // pass filename, too this.semanticFindings.recordObjectImport(instanceNamePart, filenamePart); this._ensureObjectFileExists(filenamePart, lineNbr - 1, line, startingOffset); } else if (remainingNonCommentLineStr.length > 0 && !remainingNonCommentLineStr.includes(":")) { @@ -1299,7 +1299,7 @@ export class Spin2DocumentSemanticParser { const refModifiers: string[] = isPrivate ? ["static"] : []; // record ACTUAL object public/private interface this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(methodName, new RememberedToken("method", refModifiers), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(methodName, new RememberedToken("method", lineNbr - 1, refModifiers), this._declarationComment()); // reset our list of local variables this.semanticFindings.clearLocalPAsmTokensForMethod(methodName); } else { @@ -1337,7 +1337,7 @@ export class Spin2DocumentSemanticParser { labelModifiers = ["pasmInline"]; } this._logPASM(" -- Inline PASM labelName=[" + labelName + "(" + labelType + ")[" + labelModifiers + "]]"); - this.semanticFindings.setLocalPAsmTokenForMethod(this.currentMethodName, labelName, new RememberedToken(labelType, labelModifiers), lineNbr, this._declarationComment()); + this.semanticFindings.setLocalPAsmTokenForMethod(this.currentMethodName, labelName, new RememberedToken(labelType, lineNbr - 1, labelModifiers), this._declarationComment()); } } @@ -1370,7 +1370,7 @@ export class Spin2DocumentSemanticParser { if (newName.charAt(0).match(/[a-zA-Z_]/)) { this._logVAR(" -- GLBL GetVarDecl newName=[" + newName + "]"); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(newName, new RememberedToken("variable", ["instance"]), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(newName, new RememberedToken("variable", lineNbr - 1, ["instance"]), this._declarationComment()); } } } else if (!hasGoodType && lineParts.length > 0) { @@ -1379,7 +1379,7 @@ export class Spin2DocumentSemanticParser { if (longVarName.charAt(0).match(/[a-zA-Z_]/)) { this._logVAR(" -- GLBL GetVarDecl newName=[" + longVarName + "]"); this.semanticFindings.recordDeclarationLine(line, lineNbr); - this.semanticFindings.setGlobalToken(longVarName, new RememberedToken("variable", ["instance"]), lineNbr, this._declarationComment()); + this.semanticFindings.setGlobalToken(longVarName, new RememberedToken("variable", lineNbr - 1, ["instance"]), this._declarationComment()); } } } @@ -2306,7 +2306,7 @@ export class Spin2DocumentSemanticParser { ptTokenModifiers: ["declaration", "readonly", "local"], }); // remember so we can ID references - this.semanticFindings.setLocalTokenForMethod(methodName, paramName, new RememberedToken("parameter", ["readonly", "local"]), lineNbr, this._declarationComment()); // TOKEN SET in _report() + this.semanticFindings.setLocalTokenForMethod(methodName, paramName, new RememberedToken("parameter", lineNbr - 1, ["readonly", "local"]), this._declarationComment()); // TOKEN SET in _report() currentOffset = nameOffset + paramName.length; } } @@ -2347,7 +2347,7 @@ export class Spin2DocumentSemanticParser { ptTokenModifiers: ["declaration", "local"], }); // remember so we can ID references - this.semanticFindings.setLocalTokenForMethod(methodName, returnValueName, new RememberedToken("returnValue", ["local"]), lineNbr, this._declarationComment()); // TOKEN SET in _report() + this.semanticFindings.setLocalTokenForMethod(methodName, returnValueName, new RememberedToken("returnValue", lineNbr - 1, ["local"]), this._declarationComment()); // TOKEN SET in _report() currentOffset = nameOffset + returnValueName.length; } } @@ -2432,7 +2432,7 @@ export class Spin2DocumentSemanticParser { ptTokenModifiers: ["declaration", "local"], }); // remember so we can ID references - this.semanticFindings.setLocalTokenForMethod(methodName, namedIndexPart, new RememberedToken("variable", ["local"]), lineNbr, this._declarationComment()); // TOKEN SET in _report() + this.semanticFindings.setLocalTokenForMethod(methodName, namedIndexPart, new RememberedToken("variable", lineNbr - 1, ["local"]), this._declarationComment()); // TOKEN SET in _report() } } } @@ -2451,7 +2451,7 @@ export class Spin2DocumentSemanticParser { ptTokenModifiers: ["declaration", "local"], }); // remember so we can ID references - this.semanticFindings.setLocalTokenForMethod(methodName, localName, new RememberedToken("variable", ["local"]), lineNbr, this._declarationComment()); // TOKEN SET in _report() + this.semanticFindings.setLocalTokenForMethod(methodName, localName, new RememberedToken("variable", lineNbr - 1, ["local"]), this._declarationComment()); // TOKEN SET in _report() } else { // have modifier! if (this.parseUtils.isStorageType(localName)) { diff --git a/spin2/server/src/providers/DefinitionProvider.ts b/spin2/server/src/providers/DefinitionProvider.ts new file mode 100644 index 0000000..e4efa81 --- /dev/null +++ b/spin2/server/src/providers/DefinitionProvider.ts @@ -0,0 +1,201 @@ +"use strict"; +// src/extensions.ts + +import * as lsp from "vscode-languageserver"; +import { Provider } from "."; +import { Context } from "../context"; +//import { getDefinitions } from "../symbols"; +import * as path from "path"; +import { Position, Hover, MarkupKind, Definition, Location } from "vscode-languageserver-types"; +import { DocumentFindings, ILocationOfToken } from "../parser/spin.semantic.findings"; +import { fileSpecFromURI } from "../parser/lang.utils"; +import { IDefinitionInfo, ExtensionUtils } from "../parser/spin.extension.utils"; +import { Range, TextDocument } from "vscode-languageserver-textdocument"; +import { DocumentLineAt } from "../parser/lsp.textDocument.utils"; +import { URI } from "vscode-uri"; + +export interface NamedSymbol { + location: Location; + name: string; +} + +export interface Literal { + location: Location; + text: string; +} +/* +export interface Definition extends NamedSymbol { + type: DefinitionType; + selectionRange: lsp.Range; + locals?: Map; + comment?: string; +} +*/ + +export enum DefinitionType { + Section = "section", + Label = "label", + Constant = "constant", + Variable = "variable", + Register = "register", + RegisterList = "register_list", + Offset = "offset", + Macro = "macro", + XRef = "xref", +} + +export interface FindingsAtPostion { + position: Position; + objectReference: string; + selectedWord: string; +} + +export default class DefinitionProvider implements Provider { + private defnLogEnabled: boolean = false; // WARNING (REMOVE BEFORE FLIGHT)- change to 'false' - disable before commit + private bLogStarted: boolean = false; + private extensionUtils: ExtensionUtils; + + constructor(protected readonly ctx: Context) { + this.extensionUtils = new ExtensionUtils(ctx, this.defnLogEnabled); + if (this.defnLogEnabled) { + if (this.bLogStarted == false) { + this.bLogStarted = true; + this._logMessage("Spin Hover log started."); + } else { + this._logMessage("\n\n------------------ NEW FILE ----------------\n\n"); + } + } + } + /** + * Write message to debug log (when debug enabled) + * @param message - text to be written + * @returns nothing + */ + private _logMessage(message: string): void { + if (this.defnLogEnabled) { + //Write to output window. + this.ctx.logger.log(message); + } + } + + async handleGetDefinitions({ textDocument, position }: lsp.DefinitionParams): Promise { + const defLocation: Location[] = []; + const docFSpec: string = fileSpecFromURI(textDocument.uri); + const processed = this.ctx.docsByFSpec.get(docFSpec); + if (!processed) { + return []; + } + const documentFindings: DocumentFindings | undefined = this.ctx.docsByFSpec.get(docFSpec)?.parseResult; + if (!documentFindings) { + return []; // empty case + } + const symbolsFound: DocumentFindings = documentFindings; + symbolsFound.enableLogging(this.ctx, this.defnLogEnabled); + + const symbolIdent: FindingsAtPostion | undefined = this.symbolAtLocation(processed.document, position, symbolsFound); + if (!symbolIdent) { + return []; // empty case + } + + const definitionResults: Location[] = []; + const filteredLocations: ILocationOfToken[] = this.getDefinitions(symbolIdent, symbolsFound); + this._logMessage(`+ Defn: filteredLocations=[${JSON.stringify(filteredLocations)}]`); + // for each location translate object ref to URI then build a Definition and add it to return list + for (let index = 0; index < filteredLocations.length; index++) { + const tokenLocation = filteredLocations[index]; + const uri = tokenLocation.uri; + const range: Range = { start: tokenLocation.position, end: tokenLocation.position }; + this._logMessage(`+ Defn: found Locn=[ln=${tokenLocation.position.line}, char=${tokenLocation.position.character}] in uri=[${uri}]`); + definitionResults.push({ + uri, + range, + }); + } + + return definitionResults; + } + + register(connection: lsp.Connection) { + connection.onDefinition(this.handleGetDefinitions.bind(this)); + return { + definitionProvider: true, + }; + } + + private getDefinitions(symbolAtCursor: FindingsAtPostion, symbolsFound: DocumentFindings): ILocationOfToken[] { + // given symbol at position: for all symbolSets look for name as globle token or local token, return all found + + // set this object + // for all namespaces in this object search them (recursively) + const rawLocations: ILocationOfToken[] = symbolsFound.locationsOfToken(symbolAtCursor.selectedWord); + this._logMessage(`+ Defn: objectReference=(${symbolAtCursor.objectReference}), rawLocations=[${JSON.stringify(rawLocations)}]`); + const filteredLocations: ILocationOfToken[] = []; + // for each location + for (let index = 0; index < rawLocations.length; index++) { + const tokenLocation = rawLocations[index]; + // if object is specified, return only locations from desired object + if (symbolAtCursor.objectReference.length > 0 && symbolAtCursor.objectReference.toLowerCase() !== tokenLocation.objectName) { + continue; // skip one we don't care about + } + // - translate object names/top to uri + // - then generate Definition + filteredLocations.push(tokenLocation); + } + this._logMessage(`+ Defn: getDefinitions() returning ${filteredLocations.length} locations`); + + return filteredLocations; + } + + private symbolAtLocation(document: TextDocument, position: Position, symbolsFound: DocumentFindings): FindingsAtPostion | undefined { + this._logMessage(`+ Defn: definitionLocation() ENTRY`); + const isPositionInBlockComment: boolean = symbolsFound.isLineInBlockComment(position.line); + const inPasmCodeStatus: boolean = symbolsFound.isLineInPasmCode(position.line); + const inObjDeclarationStatus: boolean = symbolsFound.isLineObjDeclaration(position.line); + const adjustedPos = this.extensionUtils.adjustWordPosition(document, position, isPositionInBlockComment, inPasmCodeStatus); + if (!adjustedPos[0]) { + this._logMessage(`+ Defn: definitionLocation() EXIT fail`); + return undefined; + } + const declarationLine: string = DocumentLineAt(document, position).trimEnd(); + let objectRef = inObjDeclarationStatus ? this._objectNameFromDeclaration(declarationLine) : adjustedPos[1]; + + const wordUnderCursor: string = adjustedPos[2]; + if (objectRef === wordUnderCursor) { + objectRef = ""; + } + const sourcePosition: Position = adjustedPos[3]; + let fileBasename = path.basename(document.uri); + this._logMessage( + `+ Defn: wordUnderCursor=[${wordUnderCursor}], inObjDecl=(${inObjDeclarationStatus}), objectRef=(${objectRef}), adjPos=(${position.line},${position.character}), file=[${fileBasename}], line=[${declarationLine}]` + ); + + return { position: sourcePosition, objectReference: objectRef, selectedWord: wordUnderCursor }; + } + + private _objectNameFromDeclaration(line: string): string { + let desiredString: string = ""; + // parse object declaration forms: + // ex: child1 : "dummy_child" | MULTIplIER = 3, CoUNT = 5 + // child1[4] : "dummy_child" | MULTIplIER = 3, CoUNT = 5 + // child1[child.MAX_CT] : "dummy_child" | MULTIplIER = 3, CoUNT = 5 + if (line.includes(":")) { + let lineParts: string[] = line.split(":"); + //this._logMessage(`+ Defn: _getObjName() :-split lineParts=[${lineParts}](${lineParts.length})`); + if (lineParts.length >= 2) { + const instanceName = lineParts[0].trim(); + if (instanceName.includes("[")) { + lineParts = instanceName.split("["); + //this._logMessage(`+ Defn: _getObjName() [-split lineParts=[${lineParts}](${lineParts.length})`); + if (lineParts.length >= 2) { + desiredString = lineParts[0].trim(); + } + } else { + desiredString = instanceName; + } + } + } + //this._logMessage(`+ Defn: _getObjName([${line}]) returns [${desiredString}]`); + + return desiredString; + } +} diff --git a/spin2/server/src/providers/HoverProvider.ts b/spin2/server/src/providers/HoverProvider.ts index 2a345ed..e6545a7 100644 --- a/spin2/server/src/providers/HoverProvider.ts +++ b/spin2/server/src/providers/HoverProvider.ts @@ -22,7 +22,7 @@ export default class HoverProvider implements Provider { private hoverLogEnabled: boolean = false; // WARNING (REMOVE BEFORE FLIGHT)- change to 'false' - disable before commit private bLogStarted: boolean = false; - private symbolsFound: DocumentFindings = new DocumentFindings(); + private symbolsFound: DocumentFindings = new DocumentFindings(); // this gets replaced private parseUtils: Spin1ParseUtils | Spin2ParseUtils = new Spin2ParseUtils(); private extensionUtils: ExtensionUtils; private spin1File: boolean = false; diff --git a/spin2/server/src/providers/SignatureHelpProvider.ts b/spin2/server/src/providers/SignatureHelpProvider.ts index 873c505..8ade96b 100644 --- a/spin2/server/src/providers/SignatureHelpProvider.ts +++ b/spin2/server/src/providers/SignatureHelpProvider.ts @@ -21,7 +21,7 @@ export default class SignatureHelpProvider implements Provider { private signatureLogEnabled: boolean = false; // WARNING (REMOVE BEFORE FLIGHT)- change to 'false' - disable before commit private bLogStarted: boolean = false; - private symbolsFound: DocumentFindings = new DocumentFindings(); + private symbolsFound: DocumentFindings = new DocumentFindings(); // this gets replaced private parseUtils: Spin1ParseUtils | Spin2ParseUtils = new Spin2ParseUtils(); private extensionUtils: ExtensionUtils; private spin1File: boolean = false; diff --git a/spin2/server/src/providers/index.ts b/spin2/server/src/providers/index.ts index 687a1c5..5392dbc 100644 --- a/spin2/server/src/providers/index.ts +++ b/spin2/server/src/providers/index.ts @@ -7,7 +7,7 @@ import { Context } from "../context"; import CompletionProvider from "./CompletionProvider"; import SemanticTokensProvider from "./SemanticTokensProvider"; // import ConfiguratonProvider from "./ConfigurationProvider"; -// import DefinitionProvider from "./DefinitionProvider"; +import DefinitionProvider from "./DefinitionProvider"; // import DocumentFormattingProvider from "./DocumentFormatttingProvider"; // import DocumentHighlightProvider from "./DocumentHighlightProvider"; // import DocumentLinkProvider from "./DocumentLinkProvider"; @@ -28,7 +28,7 @@ const providers = [ CompletionProvider, SemanticTokensProvider, // ConfiguratonProvider, - // DefinitionProvider, + DefinitionProvider, // DocumentFormattingProvider, // DocumentHighlightProvider, // DocumentLinkProvider,