Skip to content

Commit 37da615

Browse files
committed
chore: refactored whole codebase to make clear distinction between:
- extension code - fallback / per-file logic - lsp server/client and improve general organization. This likely broke some features and needs more work though....
1 parent 368766e commit 37da615

70 files changed

Lines changed: 1925 additions & 1722 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ node_modules
22
out
33
*.vsix
44
.env
5-
docs/private/*
5+
.ignored
6+
docs/private/*
7+
.ignored

package.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"vscode:prepublish": "npm run bundle",
5151
"clean:out": "node -e \"require('fs').rmSync('out', { recursive: true, force: true })\"",
5252
"build:copilot:mcp": "esbuild src/copilot/mcp/sac2c-mcp.ts --bundle --platform=node --target=node20 --format=cjs --outfile=src/copilot/mcp/sac2c-mcp.js",
53-
"bundle": "npm run clean:out && esbuild src/extension.ts src/server/server.ts --bundle --platform=node --target=node20 --format=cjs --outdir=out --outbase=src --sourcemap --external:vscode --tsconfig=tsconfig.json",
53+
"bundle": "npm run clean:out && esbuild src/extension.ts src/lsp-server/server.ts --bundle --platform=node --target=node20 --format=cjs --outdir=out --outbase=src --sourcemap --external:vscode --tsconfig=tsconfig.json",
5454
"compile": "tsc -p ./ && tsc-alias -p tsconfig.json && npm run build:copilot:mcp",
5555
"watch": "concurrently -k \"tsc -watch -p ./\" \"tsc-alias -p tsconfig.json -w\" \"esbuild src/copilot/mcp/sac2c-mcp.ts --bundle --platform=node --target=node20 --format=cjs --outfile=src/copilot/mcp/sac2c-mcp.js --watch\"",
5656
"package": "vsce package",
@@ -275,11 +275,25 @@
275275
"default": true,
276276
"description": "Enable language server feature module. Overrides sac.languageServer.enable when set."
277277
},
278+
"sac.navigation.backend": {
279+
"type": "string",
280+
"enum": [
281+
"navjson",
282+
"symbols"
283+
],
284+
"default": "symbols",
285+
"description": "Select compiler navigation backend. navjson uses cursor-targeted JSON queries; symbols uses -symbols dump output."
286+
},
278287
"sac.features.formatter.enable": {
279288
"type": "boolean",
280289
"default": true,
281290
"description": "Enable formatter feature module registration."
282291
},
292+
"sac.features.fallback.enable": {
293+
"type": "boolean",
294+
"default": true,
295+
"description": "Enable local fallback hover and navigation providers when language server does not return a result."
296+
},
283297
"sac.features.chatParticipant.enable": {
284298
"type": "boolean",
285299
"default": true,

src/constants/regex.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// Shared regexes used across navigation, formatter, and helper parsing code.
22

33
export const IDENTIFIER_CHARS = "[A-Za-z_][A-Za-z0-9_]*";
4+
export const OPERATOR_CHARS = "[+\\-*/=<>&|!%^~]+";
5+
export const FUNCTION_NAME_CHARS = `(?:${IDENTIFIER_CHARS}|${OPERATOR_CHARS})`;
46

57
export const IDENTIFIER_PATTERN = new RegExp(IDENTIFIER_CHARS);
68

7-
export const FUNCTION_DEFINITION_HEADER_PATTERN = /^(\s*)(?:inline\s+)?(?:[A-Za-z0-9_\[\].,:<>\*\s]+)?\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/;
9+
export const FUNCTION_DEFINITION_HEADER_PATTERN = /^(\s*)(?:inline\s+)?(?:[A-Za-z0-9_\[\].,:<>\*\s]+)?(?:\b|(?=[+\-*/=<>&|!%^~]))([A-Za-z_][A-Za-z0-9_]*|[+\-*/=<>&|!%^~]+)\s*\(/;
810

911
export const FUNCTION_CALL_PATTERN = /\b([A-Za-z_][A-Za-z0-9_]*)\b(?=\s*\()/g;
1012
export const BUILTIN_FUNCTION_CALL_PATTERN = /(^|[^A-Za-z0-9_])(_[A-Za-z0-9]+(?:_[A-Za-z0-9]+)*_)(?=\s*\()/g;
@@ -18,7 +20,7 @@ export const TOP_LEVEL_HEADING_PATTERN = /^#\s+/;
1820
export const DOC_SECTION_HEADING_PATTERN = /^##+\s+(.+)$/;
1921
export const SIGNATURE_CODE_BLOCK_PATTERN = /```(?:sac)?\s*\n([\s\S]*?)```/gi;
2022

21-
export const FUNCTION_DEFINITION_CAPTURE_PATTERN = /\b([A-Za-z_][A-Za-z0-9_]*)\s*\([^;{}]*\)\s*\{/g;
23+
export const FUNCTION_DEFINITION_CAPTURE_PATTERN = /(?:\b|(?=[+\-*/=<>&|!%^~]))([A-Za-z_][A-Za-z0-9_]*|[+\-*/=<>&|!%^~]+)\s*\([^;{}]*\)\s*\{/g;
2224
export const MODULE_DECLARATION_CAPTURE_PATTERN = /^\s*module\s+([A-Za-z_][A-Za-z0-9_]*)\s*;/m;
2325
export const FUNCTION_SIGNATURE_TRAILING_PAREN_PATTERN = /\([^;{}]*\)\s*$/;
2426
export const FUNCTION_HEADER_CANDIDATE_PATTERN = /^[A-Za-z_][\w\[\]\s,:*<>]*\([^;{}]*\)\s*$/;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import * as vscode from "vscode";
2+
3+
import type { FeatureLifecycle } from "$extension/lsp-client/languageClientFeature";
4+
5+
const PARTICIPANT_ID = "sac-language-support.sac";
6+
7+
function chatResult(command: string): vscode.ChatResult {
8+
return {
9+
metadata: {
10+
command,
11+
},
12+
};
13+
}
14+
15+
function buildDefaultResponse(prompt: string): string {
16+
const normalizedPrompt = prompt.trim();
17+
if (normalizedPrompt.length === 0) {
18+
return "Share what you want to do with SaC code, for example format guards, explain diagnostics, or review overloading.";
19+
}
20+
21+
return [
22+
"I can help with SaC-specific workflows:",
23+
"",
24+
"- `/sac-diagnose`: explain likely compiler issues and suggest targeted fixes",
25+
"- `/sac-format`: apply SaC formatting conventions (including multiline guard style)",
26+
"- `/sac-overload`: design or review overload changes safely",
27+
"",
28+
`Prompt summary: ${normalizedPrompt}`,
29+
].join("\n");
30+
}
31+
32+
export class ChatParticipantFeature implements FeatureLifecycle {
33+
private participant: vscode.ChatParticipant | undefined;
34+
35+
public async activate(): Promise<void> {
36+
const enabled = vscode.workspace.getConfiguration("sac").get<boolean>("features.chatParticipant.enable", true);
37+
if (!enabled) {
38+
return;
39+
}
40+
41+
const handler: vscode.ChatRequestHandler = async (request, _context, stream): Promise<vscode.ChatResult> => {
42+
if (request.command === "sac-diagnose") {
43+
stream.markdown(
44+
"SaC diagnostics workflow:\n\n1. Run `sac2c` with minimal flags first.\n2. Fix the first parser/type error before secondary errors.\n3. Re-run and iterate until the first blocking error is resolved.",
45+
);
46+
return chatResult("sac-diagnose");
47+
}
48+
49+
if (request.command === "sac-format") {
50+
stream.markdown(
51+
"SaC guard formatting:\n\n```sac\nint safe_div(int a, int b)\n | b != 0\n , a >= 0\n{\n return a / b;\n}\n```\n",
52+
);
53+
return chatResult("sac-format");
54+
}
55+
56+
if (request.command === "sac-overload") {
57+
stream.markdown(
58+
"Overloading checklist:\n\n- Keep overload family semantics aligned.\n- Add the narrowest overload needed.\n- Call out ambiguous conversion risks.",
59+
);
60+
return chatResult("sac-overload");
61+
}
62+
63+
stream.markdown(buildDefaultResponse(request.prompt));
64+
return chatResult("default");
65+
};
66+
67+
this.participant = vscode.chat.createChatParticipant(PARTICIPANT_ID, handler);
68+
this.participant.iconPath = new vscode.ThemeIcon("symbol-key");
69+
this.participant.followupProvider = {
70+
provideFollowups(result: vscode.ChatResult): vscode.ChatFollowup[] {
71+
const command = typeof result.metadata?.command === "string" ? result.metadata.command : "default";
72+
73+
if (command === "sac-diagnose") {
74+
return [{ prompt: "show a minimal fix strategy", label: "Suggest a minimal fix strategy" }];
75+
}
76+
77+
if (command === "sac-format") {
78+
return [{ prompt: "apply guard formatting to my function", label: "Format guard lines" }];
79+
}
80+
81+
return [
82+
{ prompt: "diagnose this SaC compiler error", label: "Diagnose compiler error" },
83+
{ prompt: "format this SaC function", label: "Format SaC function" },
84+
];
85+
},
86+
};
87+
}
88+
89+
public async deactivate(): Promise<void> {
90+
this.participant?.dispose();
91+
this.participant = undefined;
92+
}
93+
}

src/extension.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type ExtensionFeatureController, registerExtensionFeatures } from "$extension/features";
2+
import { Logger } from "$util/logging";
23
import * as vscode from "vscode";
34

45
let controller: ExtensionFeatureController | undefined;
@@ -15,9 +16,14 @@ let controller: ExtensionFeatureController | undefined;
1516
*/
1617
export async function activate(context: vscode.ExtensionContext): Promise<void> {
1718
try {
19+
Logger.init(context);
20+
Logger.setOutputChannel("SaC Extension");
21+
Logger.info("[extension] Activating...");
1822
controller = await registerExtensionFeatures(context);
23+
Logger.info("[extension] Activation complete.");
1924
} catch (error) {
2025
const message = error instanceof Error ? error.message : String(error);
26+
Logger.error(`[extension] Activation failed: ${message}`);
2127
vscode.window.showErrorMessage(`SaC extension activation failed: ${message}`);
2228
controller = undefined;
2329
}
@@ -29,10 +35,14 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
2935
* @returns Promise resolved when shutdown is complete.
3036
*/
3137
export async function deactivate(): Promise<void> {
38+
Logger.info("[extension] Deactivating...");
3239
if (!controller) {
40+
Logger.info("[extension] No active controller to dispose.");
3341
return;
3442
}
3543

3644
await controller.dispose();
3745
controller = undefined;
46+
Logger.info("[extension] Deactivation complete.");
47+
Logger.dispose();
3848
}
Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,11 @@ import * as vscode from "vscode";
22

33
import { SAC_CONFIG_SECTION } from "$constants/language";
44
import { registerExtensionCommands } from "$extension/commands";
5-
import type { FeatureLifecycle } from "$extension/features/languageClientFeature";
5+
import type { FeatureLifecycle } from "$extension/lsp-client/languageClientFeature";
66

7-
/**
8-
* Command feature wrapper to keep command registration modular.
9-
*/
107
export class CommandFeature implements FeatureLifecycle {
118
private readonly disposables: vscode.Disposable[] = [];
129

13-
/**
14-
* Registers extension commands when feature is enabled.
15-
*/
1610
public async activate(): Promise<void> {
1711
const enabled = vscode.workspace.getConfiguration(SAC_CONFIG_SECTION).get<boolean>("features.commands.enable", true);
1812
if (!enabled) {
@@ -22,9 +16,6 @@ export class CommandFeature implements FeatureLifecycle {
2216
this.disposables.push(...registerExtensionCommands());
2317
}
2418

25-
/**
26-
* Unregisters extension commands.
27-
*/
2819
public async deactivate(): Promise<void> {
2920
while (this.disposables.length > 0) {
3021
const disposable = this.disposables.pop();

src/extension/commands/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@ import type * as vscode from "vscode";
33
import { generateFormatConfigCommand } from "$extension/commands/generateFormatConfigCommand";
44
import { runSac2cCommand } from "$extension/commands/runSac2cCommand";
55
import type { ExtensionCommand } from "$extension/commands/types";
6+
import { Logger } from "$util/logging";
67

78
const COMMANDS: ExtensionCommand[] = [runSac2cCommand, generateFormatConfigCommand];
89

910
/**
1011
* Registers all extension commands and returns disposables.
1112
*/
1213
export function registerExtensionCommands(): vscode.Disposable[] {
13-
return COMMANDS.map((command) => command.register());
14+
Logger.info(`[commands] Registering ${COMMANDS.length} command(s)...`);
15+
const disposables = COMMANDS.map((command) => {
16+
Logger.info(`[commands] Registering command: ${command.id}`);
17+
return command.register();
18+
});
19+
Logger.info(`[commands] All ${COMMANDS.length} command(s) registered.`);
20+
return disposables;
1421
}

src/extension/commands/runSac2cCommand.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import * as vscode from "vscode";
22

33
import { SAC_CONFIG_SECTION, SAC_LANGUAGE_ID, SAC_URI_FILE_SCHEME } from "$constants/language";
4-
import type { SacSettings } from "$extension/settings/settings";
5-
import { getDefaultSettings } from "$extension/settings/settings";
4+
import type { SacSettings } from "$extension/settings";
5+
import { getDefaultSettings } from "$extension/settings";
66
import { createInvocation, isLikelyMessagingFlagFailure, runSac2c } from "$sac2c/runtime/compilerRuntime";
77

88
import type { ExtensionCommand } from "$extension/commands/types";
9+
import { Logger } from "$util/logging";
910

1011
const RUN_ACTIVE_FILE_COMMAND_ID = "sac.runActiveFile";
1112
const SAC_OUTPUT_CHANNEL_NAME = "SaC Compiler";
@@ -122,17 +123,21 @@ async function runWithOptionalMessagingRetry(
122123
}
123124

124125
async function runSac2cForResource(resource?: unknown): Promise<void> {
126+
Logger.info("[command] sac.runActiveFile invoked");
125127
const document = resolveTargetDocument(resource);
126128
if (!document) {
129+
Logger.warn("[command] No active document found.");
127130
vscode.window.showWarningMessage("No active document to run with sac2c.");
128131
return;
129132
}
130133

131134
if (!ensureRunnableSacDocument(document)) {
135+
Logger.warn("[command] Document is not a valid .sac file.");
132136
return;
133137
}
134138

135139
if (!(await ensureSavedDocument(document))) {
140+
Logger.warn("[command] Document not saved, aborting.");
136141
return;
137142
}
138143

@@ -144,6 +149,8 @@ async function runSac2cForResource(resource?: unknown): Promise<void> {
144149
const workspaceRoot = resolveWorkspaceRoot(document.uri);
145150
const fsPath = document.uri.fsPath;
146151

152+
Logger.info(`[command] Running sac2c on ${fsPath}`);
153+
Logger.info(`[command] Compiler channel: ${settings.compilerChannel}`);
147154
output.appendLine(`Running sac2c for ${fsPath}`);
148155
output.appendLine("");
149156

src/extension/fallback/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# src/extension/fallback
2+
The code inside this folder is "oppurtunistic" / simple code to perform most basic LSP IDE features without invoking a compiler or actual Language Server. The goal of this code is to provide a basic level of functionality even when the compiler is not working, and to provide a fallback for features that are not yet implemented in the LSP server. This is meant to be a temporary solution until the LSP server is fully implemented, and should not be relied upon for long-term use.
3+
4+
## Features
5+
- Formatter: Biased simple formatter based on bracket and indentation counting.
6+
- Hover information: This feature provides hover information for symbols in the code, such as their type and documentation. It is implemented by parsing the source code and extracting relevant information about the symbols.
7+
- Navigation: This feature allows the user to navigate to the definition of a symbol by clicking on it or using a keyboard shortcut.
8+
- Outline: This feature provides an outline view of the symbols in the current file, allowing the user to quickly navigate to different parts of the code.

0 commit comments

Comments
 (0)