Skip to content
Open
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
22 changes: 16 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@
},
{
"command": "vscode-objectscript.showClassDocumentationPreview",
"when": "editorLangId == objectscript-class && activeCustomEditorId == ''"
"when": "false"
},
{
"command": "vscode-objectscript.showAllClassMembers",
"when": "false"
},
{
"command": "vscode-objectscript.exportCurrentFile",
Expand Down Expand Up @@ -535,14 +539,14 @@
"command": "vscode-objectscript.showClassDocumentationPreview",
"group": "navigation@3",
"when": "editorLangId == objectscript-class && activeCustomEditorId == ''"
},
{
"command": "vscode-objectscript.showAllClassMembers",
"group": "[email protected]",
"when": "vscode-objectscript.connectActive && editorLangId == objectscript-class && activeCustomEditorId == ''"
}
],
"editor/title/context": [
{
"command": "vscode-objectscript.showClassDocumentationPreview",
"group": "1_open",
"when": "resourceLangId == objectscript-class && activeCustomEditorId == ''"
},
{
"command": "vscode-objectscript.showRESTDebugWebview",
"group": "1_open",
Expand Down Expand Up @@ -1194,6 +1198,12 @@
"command": "vscode-objectscript.openISCDocument",
"title": "Open InterSystems Document...",
"icon": "$(go-to-file)"
},
{
"category": "ObjectScript",
"command": "vscode-objectscript.showAllClassMembers",
"title": "Show All Class Members",
"icon": "$(symbol-class)"
}
],
"keybindings": [
Expand Down
5 changes: 3 additions & 2 deletions src/commands/documaticPreviewPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ export class DocumaticPreviewPanel {
*/
public static currentPanel: DocumaticPreviewPanel | undefined;

public static create(): void {
public static create(uri: vscode.Uri): void {
// Get the open document and check that it's an ObjectScript class
const openEditor = vscode.window.activeTextEditor;
const uriString = uri.toString();
const openEditor = vscode.window.visibleTextEditors.find((e) => e.document.uri.toString() == uriString);
if (openEditor === undefined) {
// Need an open document to preview
return;
Expand Down
176 changes: 176 additions & 0 deletions src/commands/showAllClassMembers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import * as vscode from "vscode";
import { AtelierAPI } from "../api";
import { clsLangId, lsExtensionId } from "../extension";
import { currentFile, handleError, stripClassMemberNameQuotes } from "../utils";
import { DocumentContentProvider } from "../providers/DocumentContentProvider";

export async function showAllClassMembers(uri: vscode.Uri): Promise<void> {
try {
// Determine the name of the class
const uriString = uri.toString();
const textDocument = vscode.workspace.textDocuments.find((td) => td.uri.toString() == uriString);
if (textDocument?.languageId != clsLangId) {
vscode.window.showErrorMessage("The document in the active text editor is not a class definition.", "Dismiss");
return;
}
const file = currentFile(textDocument);
if (!file) {
vscode.window.showErrorMessage("The class definition in the active text editor is malformed.", "Dismiss");
return;
}
const cls = file.name.slice(0, -4);
const api = new AtelierAPI(file.uri);
if (!api.active) {
vscode.window.showErrorMessage("Showing all members of a class requires an active server connection.", "Dismiss");
return;
}
// Get an array of all members
const members: {
Name: string;
Origin: string;
MemberType: "f" | "i" | "m" | "p" | "j" | "a" | "q" | "s" | "t" | "x";
Info: string;
}[] = await api
.actionQuery(
`SELECT Name, Origin, MemberType, Info FROM (
SELECT Name, Origin, 'f' AS MemberType, Parent, Internal, NotInheritable, '('||REPLACE(Properties,',',', ')||') References '||ReferencedClass||(CASE WHEN ReferencedKey IS NOT NULL THEN '('||ReferencedKey||')' ELSE '' END) AS Info FROM %Dictionary.CompiledForeignKey UNION
SELECT Name, Origin, 'i' AS MemberType, Parent, Internal, NotInheritable, (CASE WHEN Properties LIKE '%,%' THEN 'On ('||REPLACE(Properties,',',', ')||') ' WHEN Properties IS NOT NULL THEN 'On '||Properties||' ' ELSE '' END)||(CASE WHEN Type IS NOT NULL THEN '[ Type = '||Type||' ]' ELSE '' END) AS Info FROM %Dictionary.CompiledIndex WHERE NOT (Name %STARTSWITH '$') UNION
SELECT Name, Origin, 'm' AS MemberType, Parent, Internal, NotInheritable, '('||(CASE WHEN FormalSpec IS NULL THEN '' ELSE REPLACE(REPLACE(FormalSpec,',',', '),'=',' = ') END)||')'||(CASE WHEN ReturnType IS NOT NULL THEN ' As '||ReturnType||(CASE WHEN ReturnTypeParams IS NOT NULL THEN '('||REPLACE(ReturnTypeParams,'=',' = ')||')' ELSE '' END) ELSE '' END) AS Info FROM %Dictionary.CompiledMethod WHERE Stub IS NULL UNION
SELECT Name, Origin, 'p' AS MemberType, Parent, Internal, NotInheritable, CASE WHEN Expression IS NOT NULL THEN Expression WHEN _Default IS NOT NULL THEN _Default ELSE Type END AS Info FROM %Dictionary.CompiledParameter UNION
SELECT Name, Origin, 'j' AS MemberType, Parent, Internal, NotInheritable, Type AS Info FROM %Dictionary.CompiledProjection UNION
SELECT Name, Origin, 'a' AS MemberType, Parent, Internal, NotInheritable, CASE WHEN Collection IS NOT NULL THEN Collection||' Of '||Type ELSE Type END AS Info FROM %Dictionary.CompiledProperty UNION
SELECT Name, Origin, 'q' AS MemberType, Parent, Internal, NotInheritable, '('||(CASE WHEN FormalSpec IS NULL THEN '' ELSE REPLACE(REPLACE(FormalSpec,',',', '),'=',' = ') END)||') As '||Type AS Info FROM %Dictionary.CompiledQuery UNION
SELECT Name, Origin, 's' AS MemberType, Parent, Internal, NotInheritable, Type AS Info FROM %Dictionary.CompiledStorage UNION
SELECT Name, Origin, 't' AS MemberType, Parent, Internal, NotInheritable, Event||' '||_Time||' '||Foreach AS Info FROM %Dictionary.CompiledTrigger UNION
SELECT Name, Origin, 'x' AS MemberType, Parent, Internal, 0 AS NotInheritable, MimeType||(CASE WHEN SUBSTR(MimeType,-4) = '/xml' AND XMLNamespace IS NOT NULL THEN ' ('||XMLNamespace||')' ELSE '' END) AS Info FROM %Dictionary.CompiledXData
) WHERE Parent = ? AND ((NotInheritable = 0 AND Internal = 0) OR (Origin = Parent)) ORDER BY Name`.replaceAll(
"\n",
" "
),
[cls]
)
.then((data) => data?.result?.content ?? []);
if (!members.length) {
vscode.window.showWarningMessage(
"The server returned no members for this class. If members are expected, re-compile the class then try again.",
"Dismiss"
);
return;
}
// Prompt the user to pick one
const member = await vscode.window.showQuickPick(
// Convert the query rows into QuickPickItems
members.map((m) => {
const [iconId, memberType] = (() => {
switch (m.MemberType) {
case "m":
return ["method", "Method"];
case "q":
return ["function", "Query"];
case "t":
return ["event", "Trigger"];
case "p":
return ["constant", "Parameter"];
case "i":
return ["array", "Index"];
case "f":
return ["key", "ForeignKey"];
case "x":
return ["struct", "XData"];
case "s":
return ["object", "Storage"];
case "j":
return ["interface", "Projection"];
default:
return ["property", "Property"];
}
})();
let detail = m.Info;
if ("mq".includes(m.MemberType)) {
// Need to beautify the argument list
detail = "";
let inQuotes = false;
let braceDepth = 0;
for (const c of m.Info) {
if (c == '"') {
inQuotes = !inQuotes;
detail += c;
continue;
}
if (!inQuotes) {
if (c == "{") {
braceDepth++;
detail += c;
continue;
} else if (c == "}") {
braceDepth = Math.max(0, braceDepth - 1);
detail += c;
continue;
}
}
if (!inQuotes && braceDepth == 0 && ":&*=".includes(c)) {
detail += c == ":" ? " As " : c == "&" ? "ByRef " : c == "*" ? "Output " : " = ";
} else {
detail += c;
}
}
}
return {
label: m.Name,
description: m.Origin,
detail,
iconPath: new vscode.ThemeIcon(`symbol-${iconId}`),
memberType,
};
}),
{
title: `All members of ${cls}`,
placeHolder: "Pick a member to show it in the editor",
}
);
if (!member) return;
// Show the picked member
const targetUri =
member.description == cls
? uri
: DocumentContentProvider.getUri(
`${member.description}.cls`,
undefined,
undefined,
undefined,
vscode.workspace.getWorkspaceFolder(uri)?.uri
);
const symbols = (
await vscode.commands.executeCommand<vscode.DocumentSymbol[]>("vscode.executeDocumentSymbolProvider", targetUri)
)[0]?.children;
// Find the symbol for this member
const memberType = member.memberType.toLowerCase();
const symbol = symbols?.find(
(s) =>
stripClassMemberNameQuotes(s.name) == member.label &&
(memberType == "method"
? s.detail.toLowerCase().includes(memberType)
: memberType == "property"
? ["property", "relationship"].includes(s.detail.toLowerCase())
: s.detail.toLowerCase() == memberType)
);
if (!symbol) {
vscode.window.showErrorMessage(
`Did not find ${member.memberType} '${member.label}' in class '${member.description}'.`,
"Dismiss"
);
return;
}
// If Language Server is active, selectionRange is the member name.
// Else, range is the first line of the member definition excluding description.
const position = vscode.extensions.getExtension(lsExtensionId)?.isActive
? symbol.selectionRange.start
: symbol.range.start;
await vscode.window.showTextDocument(targetUri, {
selection: new vscode.Range(position, position),
preview: false,
});
} catch (error) {
handleError(error, "Failed to show all class members.");
}
}
71 changes: 37 additions & 34 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ import {
import { WorkspaceNode, NodeBase } from "./explorer/nodes";
import { showPlanWebview } from "./commands/showPlanPanel";
import { isfsConfig } from "./utils/FileProviderUtil";
import { showAllClassMembers } from "./commands/showAllClassMembers";

const packageJson = vscode.extensions.getExtension(extensionId).packageJSON;
const extensionVersion = packageJson.version;
Expand Down Expand Up @@ -886,21 +887,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {

documentContentProvider = new DocumentContentProvider();
fileSystemProvider = new FileSystemProvider();

explorerProvider = new ObjectScriptExplorerProvider();
vscode.window.createTreeView("ObjectScriptExplorer", {
treeDataProvider: explorerProvider,
showCollapseAll: true,
canSelectMany: true,
});

projectsExplorerProvider = new ProjectsExplorerProvider();
vscode.window.createTreeView("ObjectScriptProjectsExplorer", {
treeDataProvider: projectsExplorerProvider,
showCollapseAll: true,
canSelectMany: false,
});

posPanel = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0);
posPanel.command = "vscode-objectscript.jumpToTagAndOffset";
posPanel.show();
Expand Down Expand Up @@ -1125,11 +1113,28 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
}
}),
vscode.window.onDidChangeActiveTextEditor(async (editor) => {
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) {
if (vscode.workspace.workspaceFolders?.length > 1) {
const workspaceFolder = currentWorkspaceFolder();
if (workspaceFolder && workspaceFolder !== workspaceState.get<string>("workspaceFolder")) {
if (workspaceFolder && workspaceFolder != workspaceState.get<string>("workspaceFolder")) {
await workspaceState.update("workspaceFolder", workspaceFolder);
await checkConnection(false, editor?.document.uri);
// Only need to check when editor is undefined because
// we will always check when editor is defined below
if (!editor) await checkConnection(false);
}
}
if (editor) {
const conf = vscode.workspace.getConfiguration("objectscript");
const uriString = editor.document.uri.toString();
await checkConnection(false, editor.document.uri);
if (conf.get("autoPreviewXML") && editor.document.uri.path.toLowerCase().endsWith("xml")) {
previewXMLAsUDL(editor, true);
} else if (
conf.get("openClassContracted") &&
editor.document.languageId == clsLangId &&
!openedClasses.includes(uriString)
) {
vscode.commands.executeCommand("editor.foldLevel1");
openedClasses.push(uriString);
}
}
}),
Expand Down Expand Up @@ -1422,9 +1427,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
sendCommandTelemetryEvent("editOthers");
viewOthers(true);
}),
vscode.commands.registerCommand("vscode-objectscript.showClassDocumentationPreview", () => {
vscode.commands.registerCommand("vscode-objectscript.showClassDocumentationPreview", (uri: vscode.Uri) => {
sendCommandTelemetryEvent("showClassDocumentationPreview");
DocumaticPreviewPanel.create();
if (uri instanceof vscode.Uri) DocumaticPreviewPanel.create(uri);
}),
vscode.commands.registerCommand("vscode-objectscript.showRESTDebugWebview", () => {
sendCommandTelemetryEvent("showRESTDebugWebview");
Expand Down Expand Up @@ -1492,15 +1497,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
})
);
}),
vscode.window.onDidChangeActiveTextEditor((editor: vscode.TextEditor) => {
if (config("openClassContracted") && editor && editor.document.languageId === clsLangId) {
const uri: string = editor.document.uri.toString();
if (!openedClasses.includes(uri)) {
vscode.commands.executeCommand("editor.foldLevel1");
openedClasses.push(uri);
}
}
}),
vscode.workspace.onDidCloseTextDocument((doc: vscode.TextDocument) => {
const uri: string = doc.uri.toString();
const idx: number = openedClasses.indexOf(uri);
Expand Down Expand Up @@ -1618,13 +1614,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
terminals.splice(terminalIndex, 1);
}
}),
vscode.window.onDidChangeActiveTextEditor(async (textEditor: vscode.TextEditor) => {
if (!textEditor) return;
await checkConnection(false, textEditor.document.uri);
if (textEditor.document.uri.path.toLowerCase().endsWith(".xml") && config("autoPreviewXML")) {
return previewXMLAsUDL(textEditor, true);
}
}),
vscode.window.onDidChangeTextEditorSelection(async (event: vscode.TextEditorSelectionChangeEvent) => {
const document = event.textEditor.document;
// Avoid losing position indicator if event came from output channel or a non-active editor
Expand Down Expand Up @@ -1894,6 +1883,20 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
if (typeof args != "object") return;
showPlanWebview(args);
}),
vscode.window.createTreeView("ObjectScriptExplorer", {
treeDataProvider: explorerProvider,
showCollapseAll: true,
canSelectMany: true,
}),
vscode.window.createTreeView("ObjectScriptProjectsExplorer", {
treeDataProvider: projectsExplorerProvider,
showCollapseAll: true,
canSelectMany: false,
}),
vscode.commands.registerCommand("vscode-objectscript.showAllClassMembers", (uri: vscode.Uri) => {
sendCommandTelemetryEvent("showAllClassMembers");
if (uri instanceof vscode.Uri) showAllClassMembers(uri);
}),
// These three listeners are needed to keep track of which file events were caused by VS Code
// to support the "vscodeOnly" option for the objectscript.syncLocalChanges setting.
// They store the URIs of files that are about to be changed by VS Code.
Expand Down
2 changes: 2 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ export function outputConsole(data: string[]): void {
}

export interface CurrentFile {
/** The name of the document, like `User.Test.cls` */
name: string;
/** `uri.fsPath` */
fileName: string;
uri: vscode.Uri;
unredirectedUri?: vscode.Uri;
Expand Down