diff --git a/CHANGELOG.md b/CHANGELOG.md index 227723a7..a1985892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Auto-indent dot syntax on Enter for `objectscript`/`objectscript-int` (replicates leading dots). (#6) - Added `resolveContextExpression` command: posts current line/routine to API, inserts returned code on success, shows error otherwise. (#7) - Refactor API: extracted request util and updated endpoints (#8) + - Add Ctrl+Q shortcut to fetch “global documentation” for the current selection/line and print the response to the Output. (#14) ## [3.0.6] 09-Sep-2025 diff --git a/package.json b/package.json index 92044211..aae9a5b1 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,10 @@ "command": "vscode-objectscript.viewOthers", "when": "vscode-objectscript.connectActive" }, + { + "command": "vscode-objectscript.getGlobalDocumentation", + "when": "editorLangId =~ /^objectscript/ && vscode-objectscript.connectActive" + }, { "command": "vscode-objectscript.subclass", "when": "editorLangId =~ /^objectscript/ && vscode-objectscript.connectActive" @@ -840,6 +844,12 @@ "command": "vscode-objectscript.resolveContextExpression", "title": "Resolve Context Expression" }, + { + "category": "ObjectScript", + "command": "vscode-objectscript.getGlobalDocumentation", + "title": "Show Global Documentation", + "enablement": "vscode-objectscript.connectActive" + }, { "category": "ObjectScript", "command": "vscode-objectscript.compile", @@ -939,6 +949,12 @@ "enablement": "vscode-objectscript.connectActive", "title": "View Other" }, + { + "category": "ObjectScript", + "command": "vscode-objectscript.getGlobalDocumentation", + "enablement": "vscode-objectscript.connectActive", + "title": "Show Global Documentation" + }, { "category": "ObjectScript", "command": "vscode-objectscript.subclass", @@ -1220,6 +1236,12 @@ "mac": "Cmd+Alt+Space", "when": "editorTextFocus && editorLangId =~ /^objectscript/" }, + { + "command": "vscode-objectscript.getGlobalDocumentation", + "key": "Ctrl+Q", + "mac": "Cmd+Q", + "when": "editorTextFocus && editorLangId =~ /^objectscript/" + }, { "command": "vscode-objectscript.viewOthers", "key": "Ctrl+Shift+V", diff --git a/src/api/index.ts b/src/api/index.ts index 4bdc2073..b24cc809 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -719,6 +719,10 @@ export class AtelierAPI { }); } + public getGlobalDocumentation(body: { selectedText: string }): Promise { + return this.request(1, "POST", `${this.ns}/action/getglobaldocumentation`, body); + } + // v1+ public actionCompile(docs: string[], flags?: string, source = false): Promise { docs = docs.map((doc) => this.transformNameIfCsp(doc)); diff --git a/src/ccs/commands/globalDocumentation.ts b/src/ccs/commands/globalDocumentation.ts new file mode 100644 index 00000000..938481f7 --- /dev/null +++ b/src/ccs/commands/globalDocumentation.ts @@ -0,0 +1,48 @@ +import * as vscode from "vscode"; + +import { GlobalDocumentationClient } from "../sourcecontrol/clients/globalDocumentationClient"; +import { handleError, outputChannel } from "../../utils"; + +const sharedClient = new GlobalDocumentationClient(); + +function getSelectedOrCurrentLineText(editor: vscode.TextEditor): string { + const { selection, document } = editor; + + if (!selection || selection.isEmpty) { + return document.lineAt(selection.active.line).text.trim(); + } + + return document.getText(selection).trim(); +} + +export async function showGlobalDocumentation(): Promise { + const editor = vscode.window.activeTextEditor; + + if (!editor) { + return; + } + + const selectedText = getSelectedOrCurrentLineText(editor); + + if (!selectedText) { + void vscode.window.showErrorMessage("Selection is empty. Select text or place the cursor on a line with content."); + return; + } + + try { + const content = await sharedClient.fetch(editor.document, { selectedText }); + + if (!content || !content.trim()) { + void vscode.window.showInformationMessage("Global documentation did not return any content."); + return; + } + + outputChannel.appendLine("==================== Global Documentation ===================="); + for (const line of content.split(/\r?\n/)) { + outputChannel.appendLine(line); + } + outputChannel.show(true); + } catch (error) { + handleError(error, "Failed to retrieve global documentation."); + } +} diff --git a/src/ccs/core/types.ts b/src/ccs/core/types.ts index 0ded60a9..6c821c53 100644 --- a/src/ccs/core/types.ts +++ b/src/ccs/core/types.ts @@ -8,3 +8,7 @@ export interface SourceControlError { message: string; cause?: unknown; } +export interface GlobalDocumentationResponse { + content?: string | string[] | Record | null; + message?: string; +} diff --git a/src/ccs/index.ts b/src/ccs/index.ts index 4bc24a85..0ce8af2e 100644 --- a/src/ccs/index.ts +++ b/src/ccs/index.ts @@ -2,4 +2,6 @@ export { getCcsSettings, isFlagEnabled, type CcsSettings } from "./config/settin export { logDebug, logError, logInfo, logWarn } from "./core/logging"; export { SourceControlApi } from "./sourcecontrol/client"; export { resolveContextExpression } from "./commands/contextHelp"; +export { showGlobalDocumentation } from "./commands/globalDocumentation"; export { ContextExpressionClient } from "./sourcecontrol/clients/contextExpressionClient"; +export { GlobalDocumentationClient } from "./sourcecontrol/clients/globalDocumentationClient"; diff --git a/src/ccs/sourcecontrol/clients/globalDocumentationClient.ts b/src/ccs/sourcecontrol/clients/globalDocumentationClient.ts new file mode 100644 index 00000000..6c779661 --- /dev/null +++ b/src/ccs/sourcecontrol/clients/globalDocumentationClient.ts @@ -0,0 +1,47 @@ +import * as vscode from "vscode"; + +import { AtelierAPI } from "../../../api"; +import { getCcsSettings } from "../../config/settings"; +import { logDebug } from "../../core/logging"; +import { SourceControlApi } from "../client"; +import { ROUTES } from "../routes"; + +interface GlobalDocumentationPayload { + selectedText: string; +} + +export class GlobalDocumentationClient { + private readonly apiFactory: (api: AtelierAPI) => SourceControlApi; + + public constructor(apiFactory: (api: AtelierAPI) => SourceControlApi = SourceControlApi.fromAtelierApi) { + this.apiFactory = apiFactory; + } + + public async fetch(document: vscode.TextDocument, payload: GlobalDocumentationPayload): Promise { + const api = new AtelierAPI(document.uri); + + let sourceControlApi: SourceControlApi; + try { + sourceControlApi = this.apiFactory(api); + } catch (error) { + logDebug("Failed to create SourceControl API client for global documentation", error); + throw error; + } + + const { requestTimeout } = getCcsSettings(); + + try { + const response = await sourceControlApi.post(ROUTES.getGlobalDocumentation(), payload, { + timeout: requestTimeout, + responseType: "text", + transformResponse: [(data) => data], + validateStatus: (status) => status >= 200 && status < 300, + }); + + return typeof response.data === "string" ? response.data : ""; + } catch (error) { + logDebug("Global documentation request failed", error); + throw error; + } + } +} diff --git a/src/ccs/sourcecontrol/routes.ts b/src/ccs/sourcecontrol/routes.ts index b6a6736e..c379d7d5 100644 --- a/src/ccs/sourcecontrol/routes.ts +++ b/src/ccs/sourcecontrol/routes.ts @@ -2,6 +2,7 @@ export const BASE_PATH = "/api/sourcecontrol/vscode" as const; export const ROUTES = { resolveContextExpression: () => `/resolveContextExpression`, + getGlobalDocumentation: () => `/getGlobalDocumentation`, } as const; export type RouteKey = keyof typeof ROUTES; diff --git a/src/commands/globalDocumentation.ts b/src/commands/globalDocumentation.ts new file mode 100644 index 00000000..7ecd2c6a --- /dev/null +++ b/src/commands/globalDocumentation.ts @@ -0,0 +1,60 @@ +import * as vscode from "vscode"; + +import { AtelierAPI } from "../api"; +import { currentFile, handleError, outputChannel } from "../utils"; + +function getSelectedOrCurrentLineText(editor: vscode.TextEditor): string { + const { selection, document } = editor; + if (!selection || selection.isEmpty) { + return document.lineAt(selection.active.line).text.trim(); + } + return document.getText(selection).trim(); +} + +export async function showGlobalDocumentation(): Promise { + const file = currentFile(); + const editor = vscode.window.activeTextEditor; + + if (!file || !editor) { + return; + } + + const selectedText = getSelectedOrCurrentLineText(editor); + + if (!selectedText) { + void vscode.window.showErrorMessage("Selection is empty. Select text or place the cursor on a line with content."); + return; + } + + const api = new AtelierAPI(file.uri); + + if (!api.active) { + void vscode.window.showErrorMessage("No active connection to retrieve global documentation."); + return; + } + + try { + const response = await api.getGlobalDocumentation({ selectedText }); + const content = response?.result?.content; + let output = ""; + + if (Array.isArray(content)) { + output = content.join("\n"); + } else if (typeof content === "string") { + output = content; + } else if (content && typeof content === "object") { + output = JSON.stringify(content, null, 2); + } + + if (!output) { + void vscode.window.showInformationMessage("Global documentation did not return any content."); + return; + } + + outputChannel.appendLine("==================== Global Documentation ===================="); + outputChannel.appendLine(output); + outputChannel.show(true); + } catch (error) { + handleError(error, "Failed to retrieve global documentation."); + } +} diff --git a/src/extension.ts b/src/extension.ts index f3567c4d..0c4dd3ae 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -162,8 +162,7 @@ import { import { WorkspaceNode, NodeBase } from "./explorer/nodes"; import { showPlanWebview } from "./commands/showPlanPanel"; import { isfsConfig } from "./utils/FileProviderUtil"; -import { resolveContextExpression } from "./ccs"; - +import { resolveContextExpression, showGlobalDocumentation } from "./ccs"; const packageJson = vscode.extensions.getExtension(extensionId).packageJSON; const extensionVersion = packageJson.version; const aiKey = packageJson.aiKey; @@ -1372,6 +1371,10 @@ export async function activate(context: vscode.ExtensionContext): Promise { sendCommandTelemetryEvent("viewOthers"); viewOthers(false); }), + vscode.commands.registerCommand("vscode-objectscript.getGlobalDocumentation", () => { + sendCommandTelemetryEvent("getGlobalDocumentation"); + void showGlobalDocumentation(); + }), vscode.commands.registerCommand("vscode-objectscript.serverCommands.sourceControl", (uri?: vscode.Uri) => { sendCommandTelemetryEvent("serverCommands.sourceControl"); mainSourceControlMenu(uri);