Skip to content

Commit

Permalink
Fixes #357
Browse files Browse the repository at this point in the history
  • Loading branch information
isc-bsaviano committed Jan 3, 2025
1 parent 822261a commit 3c2f06a
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## [2.7.0] - 2025-XX-XX
- Fix issue [#357](https://github.com/intersystems/language-server/issues/357): Add completion for globals and routines

## [2.6.5] - 2024-11-13
- Fix issue [#356](https://github.com/intersystems/language-server/issues/356): Unexpected new dialog during password retrieval using Server Manager authprovider

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This is a [LSP](https://microsoft.github.io/language-server-protocol/) compliant
- [Semantic token](https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide)-based coloring for InterSystems ObjectScript classes, routines and CSP files, with support for embedded languages like SQL, Python, HTML, XML, Java, JavaScript and CSS.
- Hover information for ObjectScript commands, system functions, system variables, classes, class members, macros, preprocessor directives, class keywords, Parameter types, Storage definition keywords, and embedded SQL tables, fields and class methods and queries invoked as SQL procedures.
- [Go to definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition) for ObjectScript classes, class members, macros, routines, routine labels, class name parameters, `##super()`, and embedded SQL tables, fields and class methods and queries invoked as SQL procedures.
- Code completion for ObjectScript classes, class members, system functions, system variables, macros, include files, package imports, preprocessor directives, class keywords, class keyword values, class Parameter types and Storage definition keywords. Code completion for properties referenced using instance variable syntax (`i%PropertyName`) must be triggered manually using `Ctrl-Space` with the cursor immediately after the `i%`.
- Code completion for ObjectScript classes, class members, system functions, system variables, macros, include files, package imports, preprocessor directives, class keywords, class keyword values, class Parameter types, Storage definition keywords, routines and globals. Code completion for properties referenced using instance variable syntax (`i%PropertyName`) must be triggered manually using `Ctrl-Space` with the cursor immediately after the `i%`.
- Code completion for XML Element names, Attribute names and Attribute values within XData blocks that have the XMLNamespace keyword set to a URL that corresponds to a Studio Assist Schema (SASchema).
- Signature help for ObjectScript methods and macros that accept arguments.
- Document symbols for ObjectScript classes, routines and include files.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@
"scope": "window",
"type": "boolean",
"default": false,
"markdownDescription": "Controls whether class members with the `Internal` keyword are shown in completion lists."
"markdownDescription": "Controls whether class members with the `Internal` keyword and system globals are shown in completion lists."
},
"intersystems.language-server.completion.showGenerated": {
"scope": "window",
Expand Down
78 changes: 76 additions & 2 deletions server/src/providers/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,69 @@ async function completionInclude(server: ServerSpec): Promise<CompletionItem[]>
return result;
};

/** Return a list of globals or routines. Optionally filter using `prefix`. */
async function globalsOrRoutines(
doc: TextDocument, parsed: compressedline[], line: number, token: number,
settings: LanguageServerConfiguration, server: ServerSpec, lineText: string, prefix: string = ""
): Promise<CompletionItem[]> {
// Determine if this is a routine or global, and return null if we're in $BITLOGIC
let brk = false, parenLevel = 0, isBitlogic = false, lastCmd = "";
for (let ln = line; ln >= 0; ln--) {
if (!parsed[ln]?.length) continue;
for (let tkn = (ln == line ? token : parsed[ln].length - 1); tkn >= 0; tkn--) {
if (parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_delim_attrindex) {
const delimtext = doc.getText(Range.create(ln,parsed[ln][tkn].p,ln,parsed[ln][tkn].p+parsed[ln][tkn].c));
if (delimtext == "(") {
parenLevel++;
if (parenLevel > 0 && tkn > 0 && parsed[ln][tkn-1].l == ld.cos_langindex && parsed[ln][tkn-1].s == ld.cos_sysf_attrindex &&
doc.getText(Range.create(ln,parsed[ln][tkn-1].p,ln,parsed[ln][tkn-1].p+parsed[ln][tkn-1].c)).toLowerCase() == "$bitlogic"
) {
isBitlogic = true;
brk = true;
break;
}
}
else if (delimtext == ")") {
parenLevel--;
}
} else if (parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_command_attrindex) {
lastCmd = doc.getText(Range.create(ln,parsed[ln][tkn].p,ln,parsed[ln][tkn].p+parsed[ln][tkn].c)).toLowerCase();
brk = true;
break;
}
}
if (brk) break;
}
if (isBitlogic) return null;
const isRoutine =
// The character before the caret is part of a label or extrinsic function syntax
/[%$\d\p{L}]/u.test(lineText.slice(-2,-1)) ||
// Routine syntax without a label or extrinsic function syntax can only appear as an argument for a DO or JOB command
(["d","do","j","job"].includes(lastCmd) && parenLevel == 0) ||
// Special case needed since this DO command will be an error token instead of a command token
/(^|\s+)do?\s+\^$/i.test(lineText);
const respdata = await makeRESTRequest("POST",1,"/action/query",server,
isRoutine ? {
query: `SELECT DISTINCT $PIECE(Name,'.',1,$LENGTH(Name,'.')-1) AS Name FROM %Library.RoutineMgr_StudioOpenDialog(?,1,1,1,1,1,0,'NOT (Name %PATTERN ''.E1"."0.1"G"1N1".obj"'' AND $LENGTH(Name,''.'') > 3)')`,
parameters: [`${prefix.length ? `${prefix.slice(0,-1)}/` : ""}*.mac,*.int,*.obj`]
} : {
query: "SELECT Name FROM %SYS.GlobalQuery_NameSpaceList(,?,?,,,1,0)",
parameters: [`${prefix}*`,settings.completion.showInternal ? 1 : 0]
}
);
if (Array.isArray(respdata?.data?.result?.content) && respdata.data.result.content.length > 0) {
return respdata.data.result.content.map((item: { Name: string; }) => {
return {
label: item.Name.slice(prefix.length),
kind: isRoutine ? CompletionItemKind.Function : CompletionItemKind.Variable,
data: isRoutine ? "routine" : "global"
};
});
} else {
return null;
}
}

export async function onCompletion(params: CompletionParams): Promise<CompletionItem[] | null> {
var result: CompletionItem[] = [];
const doc = documents.get(params.textDocument.uri);
Expand Down Expand Up @@ -1063,6 +1126,7 @@ export async function onCompletion(params: CompletionParams): Promise<Completion
}
if (isCls) prevtokentype = "class";
}
const globalOrRoutineMatch = prevline.match(/\^(%?[\d\p{L}.]+)$/u);
if (prevtokentype === "class" || prevtokentype === "system") {
// This is a partial class name

Expand Down Expand Up @@ -1100,6 +1164,11 @@ export async function onCompletion(params: CompletionParams): Promise<Completion
}
}
}
else if (globalOrRoutineMatch && triggerlang == ld.cos_langindex) {
// This might be a routine or global

result = await globalsOrRoutines(doc,parsed,params.position.line,thistoken,settings,server,prevline.slice(0,-(globalOrRoutineMatch[1].length)),globalOrRoutineMatch[1]);
}
else {
// This is a class member

Expand Down Expand Up @@ -2150,11 +2219,16 @@ export async function onCompletion(params: CompletionParams): Promise<Completion
}
}
}
else if (prevline.slice(-1) == "^" && triggerlang == ld.cos_langindex) {
// This might be a routine or global

result = await globalsOrRoutines(doc,parsed,params.position.line,thistoken,settings,server,prevline);
}
return result;
}

export async function onCompletionResolve(item: CompletionItem): Promise<CompletionItem> {
if (item.data instanceof Array && item.data[0] === "class") {
if (Array.isArray(item.data) && item.data[0] === "class") {
// Get the description for this class from the server
const server: ServerSpec = await getServerSpec(item.data[2]);
const querydata: QueryData = {
Expand All @@ -2170,7 +2244,7 @@ export async function onCompletionResolve(item: CompletionItem): Promise<Complet
};
}
}
else if (item.data instanceof Array && item.data[0] === "macro" && item.documentation === undefined) {
else if (Array.isArray(item.data) && item.data[0] === "macro" && !item.documentation) {
// Get the macro definition from the server
const server: ServerSpec = await getServerSpec(item.data[1]);
const querydata = {
Expand Down
2 changes: 1 addition & 1 deletion server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ connection.onInitialize(() => {
textDocumentSync: TextDocumentSyncKind.Full,
completionProvider: {
resolveProvider: true,
triggerCharacters: [".","$","("," ","<",'"',"#"]
triggerCharacters: [".","$","("," ","<",'"',"#","^"]
},
hoverProvider: true,
definitionProvider: true,
Expand Down

0 comments on commit 3c2f06a

Please sign in to comment.