@fresh-editor/fresh-editor 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore +2 -0
- package/LICENSE +117 -0
- package/README.md +54 -0
- package/binary-install.js +212 -0
- package/binary.js +126 -0
- package/install.js +4 -0
- package/npm-shrinkwrap.json +900 -0
- package/package.json +100 -0
- package/plugins/README.md +121 -0
- package/plugins/clangd_support.md +20 -0
- package/plugins/clangd_support.ts +323 -0
- package/plugins/color_highlighter.ts +302 -0
- package/plugins/diagnostics_panel.ts +308 -0
- package/plugins/examples/README.md +245 -0
- package/plugins/examples/async_demo.ts +165 -0
- package/plugins/examples/bookmarks.ts +329 -0
- package/plugins/examples/buffer_query_demo.ts +110 -0
- package/plugins/examples/git_grep.ts +262 -0
- package/plugins/examples/hello_world.ts +93 -0
- package/plugins/examples/virtual_buffer_demo.ts +116 -0
- package/plugins/find_references.ts +357 -0
- package/plugins/git_find_file.ts +298 -0
- package/plugins/git_grep.ts +188 -0
- package/plugins/git_log.ts +1283 -0
- package/plugins/lib/fresh.d.ts +849 -0
- package/plugins/lib/index.ts +24 -0
- package/plugins/lib/navigation-controller.ts +214 -0
- package/plugins/lib/panel-manager.ts +218 -0
- package/plugins/lib/types.ts +72 -0
- package/plugins/lib/virtual-buffer-factory.ts +158 -0
- package/plugins/manual_help.ts +243 -0
- package/plugins/markdown_compose.ts +1207 -0
- package/plugins/merge_conflict.ts +1811 -0
- package/plugins/path_complete.ts +163 -0
- package/plugins/search_replace.ts +481 -0
- package/plugins/todo_highlighter.ts +204 -0
- package/plugins/welcome.ts +74 -0
- package/run-fresh.js +4 -0
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"artifactDownloadUrl": "https://github.com/sinelaw/fresh/releases/download/v0.1.4",
|
|
3
|
+
"author": "Noam Lewis",
|
|
4
|
+
"bin": {
|
|
5
|
+
"fresh": "run-fresh.js"
|
|
6
|
+
},
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"axios": "^1.12.2",
|
|
9
|
+
"axios-proxy-builder": "^0.1.2",
|
|
10
|
+
"console.table": "^0.10.0",
|
|
11
|
+
"detect-libc": "^2.1.2",
|
|
12
|
+
"rimraf": "^6.0.1"
|
|
13
|
+
},
|
|
14
|
+
"description": "A lightweight, fast terminal-based text editor with LSP support and TypeScript plugins",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"prettier": "^3.6.2"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=14",
|
|
20
|
+
"npm": ">=6"
|
|
21
|
+
},
|
|
22
|
+
"glibcMinimum": {
|
|
23
|
+
"major": 2,
|
|
24
|
+
"series": 35
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"command-line-utilities",
|
|
28
|
+
"text-editors",
|
|
29
|
+
"editor",
|
|
30
|
+
"terminal",
|
|
31
|
+
"tui",
|
|
32
|
+
"text-editor",
|
|
33
|
+
"lsp"
|
|
34
|
+
],
|
|
35
|
+
"license": "GPL-2.0",
|
|
36
|
+
"name": "@fresh-editor/fresh-editor",
|
|
37
|
+
"preferUnplugged": true,
|
|
38
|
+
"repository": "https://github.com/sinelaw/fresh",
|
|
39
|
+
"scripts": {
|
|
40
|
+
"fmt": "prettier --write **/*.js",
|
|
41
|
+
"fmt:check": "prettier --check **/*.js",
|
|
42
|
+
"postinstall": "node ./install.js"
|
|
43
|
+
},
|
|
44
|
+
"supportedPlatforms": {
|
|
45
|
+
"aarch64-apple-darwin": {
|
|
46
|
+
"artifactName": "fresh-editor-aarch64-apple-darwin.tar.xz",
|
|
47
|
+
"bins": {
|
|
48
|
+
"fresh": "fresh"
|
|
49
|
+
},
|
|
50
|
+
"zipExt": ".tar.xz"
|
|
51
|
+
},
|
|
52
|
+
"aarch64-pc-windows-msvc": {
|
|
53
|
+
"artifactName": "fresh-editor-x86_64-pc-windows-msvc.zip",
|
|
54
|
+
"bins": {
|
|
55
|
+
"fresh": "fresh.exe"
|
|
56
|
+
},
|
|
57
|
+
"zipExt": ".zip"
|
|
58
|
+
},
|
|
59
|
+
"aarch64-unknown-linux-gnu": {
|
|
60
|
+
"artifactName": "fresh-editor-aarch64-unknown-linux-gnu.tar.xz",
|
|
61
|
+
"bins": {
|
|
62
|
+
"fresh": "fresh"
|
|
63
|
+
},
|
|
64
|
+
"zipExt": ".tar.xz"
|
|
65
|
+
},
|
|
66
|
+
"x86_64-apple-darwin": {
|
|
67
|
+
"artifactName": "fresh-editor-x86_64-apple-darwin.tar.xz",
|
|
68
|
+
"bins": {
|
|
69
|
+
"fresh": "fresh"
|
|
70
|
+
},
|
|
71
|
+
"zipExt": ".tar.xz"
|
|
72
|
+
},
|
|
73
|
+
"x86_64-pc-windows-gnu": {
|
|
74
|
+
"artifactName": "fresh-editor-x86_64-pc-windows-msvc.zip",
|
|
75
|
+
"bins": {
|
|
76
|
+
"fresh": "fresh.exe"
|
|
77
|
+
},
|
|
78
|
+
"zipExt": ".zip"
|
|
79
|
+
},
|
|
80
|
+
"x86_64-pc-windows-msvc": {
|
|
81
|
+
"artifactName": "fresh-editor-x86_64-pc-windows-msvc.zip",
|
|
82
|
+
"bins": {
|
|
83
|
+
"fresh": "fresh.exe"
|
|
84
|
+
},
|
|
85
|
+
"zipExt": ".zip"
|
|
86
|
+
},
|
|
87
|
+
"x86_64-unknown-linux-gnu": {
|
|
88
|
+
"artifactName": "fresh-editor-x86_64-unknown-linux-gnu.tar.xz",
|
|
89
|
+
"bins": {
|
|
90
|
+
"fresh": "fresh"
|
|
91
|
+
},
|
|
92
|
+
"zipExt": ".tar.xz"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"version": "0.1.4",
|
|
96
|
+
"volta": {
|
|
97
|
+
"node": "18.14.1",
|
|
98
|
+
"npm": "9.5.0"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Plugins
|
|
2
|
+
|
|
3
|
+
This directory contains production-ready plugins for the editor. Plugins are automatically loaded when the editor starts.
|
|
4
|
+
|
|
5
|
+
## Available Plugins
|
|
6
|
+
|
|
7
|
+
### TODO Highlighter (`todo_highlighter.lua`)
|
|
8
|
+
|
|
9
|
+
**A complete, useful plugin demonstrating Phase 2 API capabilities.**
|
|
10
|
+
|
|
11
|
+
Highlights TODO/FIXME/HACK/NOTE/XXX/BUG keywords in comments with color-coded overlays.
|
|
12
|
+
|
|
13
|
+
**Features:**
|
|
14
|
+
- Multi-language comment support (C/C++, Python, Lua, JavaScript, HTML, etc.)
|
|
15
|
+
- Color-coded highlighting:
|
|
16
|
+
- 🟠 **TODO** - Orange (tasks to do)
|
|
17
|
+
- 🔴 **FIXME** - Red (things to fix)
|
|
18
|
+
- 🟡 **HACK** - Yellow (temporary workarounds)
|
|
19
|
+
- 🟢 **NOTE** - Green (important notes)
|
|
20
|
+
- 🟣 **XXX** - Magenta (items needing review)
|
|
21
|
+
- 🔺 **BUG** - Dark Red (known bugs)
|
|
22
|
+
- Smart comment detection (only highlights keywords in comments, not in regular text)
|
|
23
|
+
|
|
24
|
+
**Commands:**
|
|
25
|
+
- `TODO Highlighter: Toggle` - Enable/disable highlighting
|
|
26
|
+
- `TODO Highlighter: Enable` - Turn on highlighting
|
|
27
|
+
- `TODO Highlighter: Disable` - Turn off and clear highlights
|
|
28
|
+
- `TODO Highlighter: Refresh` - Re-scan current buffer
|
|
29
|
+
- `TODO Highlighter: Show Keywords` - Display tracked keywords
|
|
30
|
+
|
|
31
|
+
**Usage:**
|
|
32
|
+
1. Open any file with TODO comments
|
|
33
|
+
2. Press `Ctrl+P` to open command palette
|
|
34
|
+
3. Type "TODO" and select `TODO Highlighter: Toggle`
|
|
35
|
+
4. Keywords in comments will be highlighted!
|
|
36
|
+
|
|
37
|
+
**APIs Used:**
|
|
38
|
+
- Buffer Query API: `get_active_buffer_id()`, `get_buffer_content()`
|
|
39
|
+
- Overlay API: `add_overlay()`, `remove_overlay()`
|
|
40
|
+
- Command Registration: `register_command()`
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### Git Grep (`git-grep.lua`)
|
|
45
|
+
|
|
46
|
+
**Full-featured git grep with hook-based prompt API (Phase 2 - Jan 2025)**
|
|
47
|
+
|
|
48
|
+
Interactive search through all git-tracked files with real-time results.
|
|
49
|
+
|
|
50
|
+
**Features:**
|
|
51
|
+
- Search as you type with async git grep
|
|
52
|
+
- Shows file:line:column context for each match
|
|
53
|
+
- Opens files at exact match location
|
|
54
|
+
- Graceful handling of empty results and errors
|
|
55
|
+
- ~150 lines of Lua demonstrating prompt API
|
|
56
|
+
|
|
57
|
+
**Usage:**
|
|
58
|
+
```lua
|
|
59
|
+
start_git_grep() -- From Lua or keybinding
|
|
60
|
+
```
|
|
61
|
+
Or use command palette: "Git Grep"
|
|
62
|
+
|
|
63
|
+
**APIs Used:**
|
|
64
|
+
- Hook-based Prompt API: `start_prompt()`, `set_prompt_suggestions()`
|
|
65
|
+
- Prompt Hooks: `prompt-changed`, `prompt-confirmed`, `prompt-cancelled`
|
|
66
|
+
- Async Process: `editor.spawn()`
|
|
67
|
+
- File Navigation: `editor.open_file({path, line, column})`
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### Git Find File (`git-find-file.lua`)
|
|
72
|
+
|
|
73
|
+
**Fast fuzzy file finder for git repos (Phase 2 - Jan 2025)**
|
|
74
|
+
|
|
75
|
+
Find and open git-tracked files with fuzzy matching, similar to Ctrl+P in VSCode.
|
|
76
|
+
|
|
77
|
+
**Features:**
|
|
78
|
+
- Fuzzy file name filtering (all chars match in order)
|
|
79
|
+
- Caches git ls-files for instant filtering
|
|
80
|
+
- Shows up to 100 matches in real-time
|
|
81
|
+
- Opens selected file or manual path
|
|
82
|
+
- ~150 lines of pure Lua implementation
|
|
83
|
+
|
|
84
|
+
**Usage:**
|
|
85
|
+
```lua
|
|
86
|
+
start_git_find_file() -- From Lua or keybinding
|
|
87
|
+
```
|
|
88
|
+
Or use command palette: "Git Find File"
|
|
89
|
+
|
|
90
|
+
**APIs Used:**
|
|
91
|
+
- Same hook-based prompt API as git grep
|
|
92
|
+
- Demonstrates reusability of prompt system
|
|
93
|
+
- Pure Lua fuzzy matching algorithm
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### Welcome (`welcome.lua`)
|
|
98
|
+
|
|
99
|
+
Simple welcome message plugin that demonstrates basic plugin loading and status messages.
|
|
100
|
+
|
|
101
|
+
**Commands:**
|
|
102
|
+
- Various demo commands showing basic plugin capabilities
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Example Plugins
|
|
107
|
+
|
|
108
|
+
See `examples/` directory for educational examples demonstrating specific API features:
|
|
109
|
+
- `hello.lua` - Minimal plugin example
|
|
110
|
+
- `highlight_demo.lua` - Overlay API demonstrations
|
|
111
|
+
- `buffer_query_demo.lua` - Buffer state querying (Phase 2)
|
|
112
|
+
- `async_demo.lua` - Async process spawning (Phase 2)
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Plugin Development
|
|
117
|
+
|
|
118
|
+
For plugin development guides, see:
|
|
119
|
+
- **Quick Start:** `../PLUGINS_QUICKSTART.md`
|
|
120
|
+
- **API Reference:** `examples/README.md`
|
|
121
|
+
- **Implementation:** `../docs/PLUGIN_SYSTEM_IMPLEMENTATION.md`
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Clangd Helper Plugin
|
|
2
|
+
|
|
3
|
+
Fresh bundles `plugins/clangd_support.ts` so clangd users get a small helper plugin out of the box.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
* `Clangd: Switch Source/Header` calls `textDocument/switchSourceHeader` for the active cpp-style buffer and opens the returned URI if there is a match.
|
|
8
|
+
* `Clangd: Open Project Config` searches the current directory tree for a `.clangd` file and opens it in the editor.
|
|
9
|
+
|
|
10
|
+
Those commands are registered in the command palette after the plugin loads; TypeScript plugins can register their own commands by calling `editor.registerCommand`.
|
|
11
|
+
|
|
12
|
+
## Notifications
|
|
13
|
+
|
|
14
|
+
The plugin listens for `lsp/custom_notification` events emitted by the core and filters for clangd-specific methods (`textDocument/clangd.fileStatus`, `$/memoryUsage`, etc.). When clangd sends `textDocument/clangd.fileStatus`, the plugin surfaces it as a status message (`Clangd file status: …`). The editor renders this plugin-provided status slot alongside the usual diagnostics/cursor info, so the notification stays visible without overwriting core messages.
|
|
15
|
+
|
|
16
|
+
Use `editor.setStatus` to set a plugin status message and `editor.setStatus("")` to clear it; the core `Editor::set_status_message` call clears the plugin slot so core actions regain priority.
|
|
17
|
+
|
|
18
|
+
## Project setup heuristic
|
|
19
|
+
|
|
20
|
+
`Clangd: Project Setup` opens a readonly panel that inspects the current workspace root and reports whether the files clangd needs are present (`compile_commands.json`, `.clangd`, etc.). The panel also guesses the build system (CMake, Bazel, Make) by looking for markers like `CMakeLists.txt` or `WORKSPACE` and prints quick tips for generating the missing artifacts (e.g., `cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON build`, `bear -- make`). This panel gives you a quick readiness check before enabling heavier clangd features on projects such as Lustre or other Makefile-heavy trees.
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/// <reference path="../types/fresh.d.ts" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Clangd helper plugin
|
|
5
|
+
*
|
|
6
|
+
* Provides two commands:
|
|
7
|
+
* - Switch Source/Header (uses clangd/textDocument/switchSourceHeader)
|
|
8
|
+
* - Open project .clangd configuration file
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { PanelManager } from "./lib/index.ts";
|
|
12
|
+
|
|
13
|
+
const languageMap: Record<string, string> = {
|
|
14
|
+
c: "cpp",
|
|
15
|
+
h: "cpp",
|
|
16
|
+
hp: "cpp",
|
|
17
|
+
hpp: "cpp",
|
|
18
|
+
hxx: "cpp",
|
|
19
|
+
hh: "cpp",
|
|
20
|
+
cpp: "cpp",
|
|
21
|
+
cxx: "cpp",
|
|
22
|
+
cc: "cpp",
|
|
23
|
+
objc: "cpp",
|
|
24
|
+
mm: "cpp",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function detectLanguage(path: string): string | null {
|
|
28
|
+
const segments = path.split(".");
|
|
29
|
+
if (segments.length === 1) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const ext = segments[segments.length - 1].toLowerCase();
|
|
33
|
+
return languageMap[ext] ?? null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function pathToFileUri(path: string): string {
|
|
37
|
+
let normalized = path.replace(/\\/g, "/");
|
|
38
|
+
if (!normalized.startsWith("/")) {
|
|
39
|
+
normalized = "/" + normalized;
|
|
40
|
+
}
|
|
41
|
+
return "file://" + encodeURI(normalized);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function fileUriToPath(uri: string): string {
|
|
45
|
+
if (!uri.startsWith("file://")) {
|
|
46
|
+
return uri;
|
|
47
|
+
}
|
|
48
|
+
let path = decodeURI(uri.substring("file://".length));
|
|
49
|
+
if (path.startsWith("/") && path.length > 2 && path[2] === ":") {
|
|
50
|
+
path = path.substring(1);
|
|
51
|
+
}
|
|
52
|
+
return path;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function setClangdStatus(message: string): void {
|
|
56
|
+
editor.setStatus(message);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
globalThis.clangdSwitchSourceHeader = async function(): Promise<void> {
|
|
60
|
+
const bufferId = editor.getActiveBufferId();
|
|
61
|
+
const path = editor.getBufferPath(bufferId);
|
|
62
|
+
if (!path) {
|
|
63
|
+
setClangdStatus("Clangd: there is no active file to switch");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const language = detectLanguage(path);
|
|
68
|
+
if (!language) {
|
|
69
|
+
setClangdStatus("Clangd: unsupported file type for switch header");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const uri = pathToFileUri(path);
|
|
74
|
+
editor.debug(`clangdSwitchSourceHeader: sending request for ${uri}`);
|
|
75
|
+
try {
|
|
76
|
+
const result = await editor.sendLspRequest(language, "textDocument/switchSourceHeader", {
|
|
77
|
+
textDocument: { uri },
|
|
78
|
+
});
|
|
79
|
+
editor.debug(`clangdSwitchSourceHeader: got result ${JSON.stringify(result)}`);
|
|
80
|
+
if (typeof result === "string" && result.length > 0) {
|
|
81
|
+
const targetPath = fileUriToPath(result);
|
|
82
|
+
editor.openFile(targetPath, 0, 0);
|
|
83
|
+
setClangdStatus("Clangd: opened corresponding file");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
setClangdStatus("Clangd: no matching header/source found");
|
|
87
|
+
} catch (err) {
|
|
88
|
+
setClangdStatus(`Clangd switch source/header failed: ${err}`);
|
|
89
|
+
editor.debug(`clangdSwitchSourceHeader error: ${err}`);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const projectPanel = new PanelManager("Clangd project setup", "clangd-project-setup");
|
|
94
|
+
|
|
95
|
+
function pathDesc(path: string): string {
|
|
96
|
+
if (!path) {
|
|
97
|
+
return "unknown";
|
|
98
|
+
}
|
|
99
|
+
return path;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function detectProjectRoot(): string | null {
|
|
103
|
+
const roots = new Set<string>();
|
|
104
|
+
const cwd = editor.getCwd();
|
|
105
|
+
if (cwd) {
|
|
106
|
+
roots.add(cwd);
|
|
107
|
+
}
|
|
108
|
+
const bufferId = editor.getActiveBufferId();
|
|
109
|
+
const bufferPath = editor.getBufferPath(bufferId);
|
|
110
|
+
if (bufferPath) {
|
|
111
|
+
// Add the buffer directory and a few ancestors
|
|
112
|
+
let current = editor.pathDirname(bufferPath);
|
|
113
|
+
let depth = 0;
|
|
114
|
+
while (current && depth < 5) {
|
|
115
|
+
roots.add(current);
|
|
116
|
+
current = editor.pathDirname(current);
|
|
117
|
+
depth += 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (roots.size === 0) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return Array.from(roots)[0];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function analyzeProject(root: string | null) {
|
|
127
|
+
const lines: string[] = [];
|
|
128
|
+
const dirCandidates: string[] = [];
|
|
129
|
+
if (root) {
|
|
130
|
+
dirCandidates.push(root);
|
|
131
|
+
}
|
|
132
|
+
const currentDir = editor.getCwd();
|
|
133
|
+
if (currentDir && currentDir !== root) {
|
|
134
|
+
dirCandidates.push(currentDir);
|
|
135
|
+
}
|
|
136
|
+
const activeBufferId = editor.getActiveBufferId();
|
|
137
|
+
const bufferPath = editor.getBufferPath(activeBufferId);
|
|
138
|
+
if (bufferPath) {
|
|
139
|
+
const bufferDir = editor.pathDirname(bufferPath);
|
|
140
|
+
if (bufferDir && !dirCandidates.includes(bufferDir)) {
|
|
141
|
+
dirCandidates.push(bufferDir);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const compileCandidates = [
|
|
146
|
+
"compile_commands.json",
|
|
147
|
+
"build/compile_commands.json",
|
|
148
|
+
"out/compile_commands.json",
|
|
149
|
+
"cmake-build-debug/compile_commands.json",
|
|
150
|
+
];
|
|
151
|
+
let compilePath: string | null = null;
|
|
152
|
+
for (const base of dirCandidates) {
|
|
153
|
+
for (const relative of compileCandidates) {
|
|
154
|
+
const candidate = editor.pathJoin(base, relative);
|
|
155
|
+
if (editor.fileExists(candidate)) {
|
|
156
|
+
compilePath = candidate;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (compilePath) {
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const clangdFileCandidates = [".clangd", ".clangd/compile_flags.txt"];
|
|
166
|
+
let clangdPath: string | null = null;
|
|
167
|
+
for (const base of dirCandidates) {
|
|
168
|
+
for (const relative of clangdFileCandidates) {
|
|
169
|
+
const candidate = editor.pathJoin(base, relative);
|
|
170
|
+
if (editor.fileExists(candidate)) {
|
|
171
|
+
clangdPath = candidate;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (clangdPath) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const status: string[] = [];
|
|
181
|
+
if (root) {
|
|
182
|
+
status.push(`Project root: ${root}`);
|
|
183
|
+
} else {
|
|
184
|
+
status.push("Project root: (unknown)");
|
|
185
|
+
}
|
|
186
|
+
status.push("");
|
|
187
|
+
|
|
188
|
+
if (compilePath) {
|
|
189
|
+
status.push(`Compile commands: ready (${compilePath})`);
|
|
190
|
+
} else {
|
|
191
|
+
status.push("Compile commands: missing");
|
|
192
|
+
status.push(" Tip: run `cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON` or `bear -- make` to generate compile_commands.json and place it at the project root.");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (clangdPath) {
|
|
196
|
+
status.push(`.clangd configuration: ${clangdPath}`);
|
|
197
|
+
} else {
|
|
198
|
+
status.push(".clangd configuration: missing");
|
|
199
|
+
status.push(" Tip: create a .clangd file to customize clangd flags, fallback file, etc.");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const buildHints: string[] = [];
|
|
203
|
+
if (root) {
|
|
204
|
+
if (editor.fileExists(editor.pathJoin(root, "CMakeLists.txt"))) {
|
|
205
|
+
buildHints.push("CMake project detected (configure with `cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON`).");
|
|
206
|
+
}
|
|
207
|
+
if (
|
|
208
|
+
editor.fileExists(editor.pathJoin(root, "WORKSPACE")) ||
|
|
209
|
+
editor.fileExists(editor.pathJoin(root, "WORKSPACE.bazel")) ||
|
|
210
|
+
editor.fileExists(editor.pathJoin(root, "BUILD.bazel"))
|
|
211
|
+
) {
|
|
212
|
+
buildHints.push("Bazel project detected (use `bazel build //...` and attach the generated compile_commands.json).");
|
|
213
|
+
}
|
|
214
|
+
if (editor.fileExists(editor.pathJoin(root, "Makefile"))) {
|
|
215
|
+
buildHints.push("Makefile detected (run `bear -- make` or `intercept-build make` to emit compile_commands.json).");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (buildHints.length > 0) {
|
|
219
|
+
status.push("");
|
|
220
|
+
status.push("Build system hints:");
|
|
221
|
+
for (const hint of buildHints) {
|
|
222
|
+
status.push(` - ${hint}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
status.push("");
|
|
227
|
+
status.push("General tips:");
|
|
228
|
+
status.push(" * Place compile_commands.json at the project root or point clangd to a custom path.");
|
|
229
|
+
status.push(" * Use `Clangd: Open Project Config` to edit project-specific overrides.");
|
|
230
|
+
status.push(" * Use `Clangd: Switch Source/Header` once compile data is available.");
|
|
231
|
+
|
|
232
|
+
return status;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
globalThis.clangdProjectSetup = async function (): Promise<void> {
|
|
236
|
+
const projectRoot = detectProjectRoot();
|
|
237
|
+
const summary = analyzeProject(projectRoot);
|
|
238
|
+
const entries = summary.map((line) => ({
|
|
239
|
+
text: line + "\n",
|
|
240
|
+
properties: {},
|
|
241
|
+
}));
|
|
242
|
+
await projectPanel.open({
|
|
243
|
+
entries,
|
|
244
|
+
ratio: 0.3,
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
editor.registerCommand(
|
|
249
|
+
"Clangd: Project Setup",
|
|
250
|
+
"Analyze C/C++ clangd readiness (compile_commands.json, .clangd)",
|
|
251
|
+
"clangdProjectSetup",
|
|
252
|
+
""
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
globalThis.clangdOpenProjectConfig = function(): void {
|
|
256
|
+
const bufferId = editor.getActiveBufferId();
|
|
257
|
+
const targets = new Set<string>();
|
|
258
|
+
const bufferPath = editor.getBufferPath(bufferId);
|
|
259
|
+
if (bufferPath) {
|
|
260
|
+
const dir = editor.pathDirname(bufferPath);
|
|
261
|
+
targets.add(dir);
|
|
262
|
+
}
|
|
263
|
+
const cwd = editor.getCwd();
|
|
264
|
+
if (cwd) {
|
|
265
|
+
targets.add(cwd);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let opened = false;
|
|
269
|
+
for (const dir of Array.from(targets)) {
|
|
270
|
+
const configPath = editor.pathJoin(dir, ".clangd");
|
|
271
|
+
if (editor.fileExists(configPath)) {
|
|
272
|
+
editor.openFile(configPath, 0, 0);
|
|
273
|
+
setClangdStatus("Opened .clangd configuration");
|
|
274
|
+
opened = true;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!opened) {
|
|
280
|
+
setClangdStatus("Could not find .clangd configuration in workspace");
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
editor.registerCommand(
|
|
285
|
+
"Clangd: Switch Source/Header",
|
|
286
|
+
"Jump to header/source pair using clangd",
|
|
287
|
+
"clangdSwitchSourceHeader",
|
|
288
|
+
"normal"
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
editor.registerCommand(
|
|
292
|
+
"Clangd: Open Project Config",
|
|
293
|
+
"Open the nearest .clangd file",
|
|
294
|
+
"clangdOpenProjectConfig",
|
|
295
|
+
"normal"
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
setClangdStatus("Clangd support plugin loaded (switch header + config commands)");
|
|
299
|
+
|
|
300
|
+
globalThis.onClangdCustomNotification = function(payload: {
|
|
301
|
+
language: string;
|
|
302
|
+
method: string;
|
|
303
|
+
params: Record<string, unknown> | null;
|
|
304
|
+
}): void {
|
|
305
|
+
if (!payload || payload.language !== "cpp") {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
editor.debug(
|
|
310
|
+
`clangd notification ${payload.method}: ${JSON.stringify(payload.params)}`,
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
if (payload.method === "textDocument/clangd.fileStatus" && payload.params) {
|
|
314
|
+
const status = (payload.params as any).status ?? "unknown";
|
|
315
|
+
editor.debug(`Clangd file status: ${JSON.stringify(status)}`);
|
|
316
|
+
setClangdStatus(`Clangd file status: ${status}`);
|
|
317
|
+
} else if (payload.method === "$/memoryUsage" && payload.params) {
|
|
318
|
+
const usage = (payload.params as any).used ?? "unknown";
|
|
319
|
+
editor.debug(`Clangd memory usage: ${usage}`);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
editor.on("lsp/custom_notification", "onClangdCustomNotification");
|