nvim-keymap-migrator 1.0.0
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/LICENSE +674 -0
- package/README.md +193 -0
- package/index.js +423 -0
- package/package.json +41 -0
- package/src/config.js +207 -0
- package/src/detector.js +401 -0
- package/src/extractor.js +265 -0
- package/src/generators/intellij.js +178 -0
- package/src/generators/vimrc.js +118 -0
- package/src/generators/vscode.js +175 -0
- package/src/install.js +379 -0
- package/src/namespace.js +63 -0
- package/src/registry.js +47 -0
- package/src/report.js +81 -0
- package/src/utils.js +36 -0
- package/templates/aliases.json +31 -0
- package/templates/defaults.json +11 -0
- package/templates/editing-mappings.json +34 -0
- package/templates/git-mappings.json +38 -0
- package/templates/lsp-mappings.json +62 -0
- package/templates/navigation-mappings.json +50 -0
package/src/config.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
|
|
6
|
+
const START_MARKER = "__NVIM_KEYMAP_MIGRATOR_CONFIG_START__";
|
|
7
|
+
const END_MARKER = "__NVIM_KEYMAP_MIGRATOR_CONFIG_END__";
|
|
8
|
+
|
|
9
|
+
const INJECT_LUA = `
|
|
10
|
+
local warnings = {}
|
|
11
|
+
_G.__nkm_cfg_source_ok = nil
|
|
12
|
+
|
|
13
|
+
local function add_warning(message)
|
|
14
|
+
table.insert(warnings, tostring(message))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
function _G.__nkm_cfg_source_user_config()
|
|
18
|
+
local config_path = vim.fn.stdpath('config')
|
|
19
|
+
local init_lua = config_path .. '/init.lua'
|
|
20
|
+
local init_vim = config_path .. '/init.vim'
|
|
21
|
+
local source_ok = false
|
|
22
|
+
|
|
23
|
+
if vim.fn.filereadable(init_lua) == 1 then
|
|
24
|
+
local ok, err = pcall(dofile, init_lua)
|
|
25
|
+
source_ok = ok
|
|
26
|
+
if not ok then
|
|
27
|
+
add_warning('failed_to_source_init_lua: ' .. tostring(err))
|
|
28
|
+
end
|
|
29
|
+
elseif vim.fn.filereadable(init_vim) == 1 then
|
|
30
|
+
local ok, err = pcall(vim.cmd, 'source ' .. vim.fn.fnameescape(init_vim))
|
|
31
|
+
source_ok = ok
|
|
32
|
+
if not ok then
|
|
33
|
+
add_warning('failed_to_source_init_vim: ' .. tostring(err))
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
add_warning('no_init_file_found')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
_G.__nkm_cfg_source_ok = source_ok
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
function _G.__nkm_cfg_emit(mode_label)
|
|
43
|
+
local leader = vim.g.mapleader
|
|
44
|
+
local maplocalleader = vim.g.maplocalleader
|
|
45
|
+
local mapleader_set = (leader ~= nil or maplocalleader ~= nil)
|
|
46
|
+
|
|
47
|
+
local payload = {
|
|
48
|
+
leader = leader ~= nil and tostring(leader) or (maplocalleader ~= nil and tostring(maplocalleader) or '\\\\'),
|
|
49
|
+
mapleader_set = mapleader_set,
|
|
50
|
+
maplocalleader = maplocalleader ~= nil and tostring(maplocalleader) or nil,
|
|
51
|
+
config_path = vim.fn.stdpath('config'),
|
|
52
|
+
source_ok = _G.__nkm_cfg_source_ok,
|
|
53
|
+
mode = mode_label,
|
|
54
|
+
warnings = warnings,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
io.write('${START_MARKER}\\n')
|
|
58
|
+
io.write(vim.json.encode(payload))
|
|
59
|
+
io.write('\\n${END_MARKER}\\n')
|
|
60
|
+
vim.cmd('qa!')
|
|
61
|
+
end
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
export async function detectConfig() {
|
|
65
|
+
const tempDir = await mkdtemp(join(tmpdir(), "nvim-keymap-migrator-config-"));
|
|
66
|
+
const scriptPath = join(tempDir, "config.lua");
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await writeFile(scriptPath, INJECT_LUA, "utf8");
|
|
70
|
+
|
|
71
|
+
let runtimeError = null;
|
|
72
|
+
let payload = null;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
payload = await runConfigDetection(scriptPath, tempDir, "runtime");
|
|
76
|
+
} catch (error) {
|
|
77
|
+
runtimeError = error;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!payload) {
|
|
81
|
+
payload = await runConfigDetection(scriptPath, tempDir, "strict");
|
|
82
|
+
payload.mode = "strict";
|
|
83
|
+
payload.fallback_from = "runtime";
|
|
84
|
+
payload.fallback_reason = summarizeError(runtimeError);
|
|
85
|
+
payload.warnings = Array.isArray(payload.warnings)
|
|
86
|
+
? payload.warnings
|
|
87
|
+
: [];
|
|
88
|
+
payload.warnings.unshift(
|
|
89
|
+
`runtime_config_detection_failed: ${summarizeError(runtimeError)}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
leader: payload.leader ?? "\\",
|
|
95
|
+
mapleader_set: Boolean(payload.mapleader_set),
|
|
96
|
+
maplocalleader: payload.maplocalleader ?? null,
|
|
97
|
+
config_path: payload.config_path ?? null,
|
|
98
|
+
source_ok: payload.source_ok ?? null,
|
|
99
|
+
mode: payload.mode ?? "unknown",
|
|
100
|
+
fallback_from: payload.fallback_from ?? null,
|
|
101
|
+
fallback_reason: payload.fallback_reason ?? null,
|
|
102
|
+
warnings: Array.isArray(payload.warnings) ? payload.warnings : [],
|
|
103
|
+
};
|
|
104
|
+
} finally {
|
|
105
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function runConfigDetection(scriptPath, tempDir, mode) {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const logPath = join(tempDir, `nvim-config-${mode}.log`);
|
|
112
|
+
const args = ["--headless"];
|
|
113
|
+
|
|
114
|
+
if (mode === "strict") {
|
|
115
|
+
args.push("-u", "NONE");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
args.push("--cmd", `lua dofile([[${scriptPath}]])`);
|
|
119
|
+
|
|
120
|
+
if (mode === "strict") {
|
|
121
|
+
args.push("-c", "lua _G.__nkm_cfg_source_user_config()");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
args.push("-c", `lua _G.__nkm_cfg_emit('${mode}')`);
|
|
125
|
+
|
|
126
|
+
const nvim = spawn("nvim", args, {
|
|
127
|
+
env: {
|
|
128
|
+
...process.env,
|
|
129
|
+
NVIM_LOG_FILE: logPath,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
let stdout = "";
|
|
134
|
+
let stderr = "";
|
|
135
|
+
|
|
136
|
+
nvim.on("error", (error) => {
|
|
137
|
+
reject(new Error(`Failed to start Neovim process: ${error.message}`));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
nvim.stdout.on("data", (data) => {
|
|
141
|
+
stdout += data.toString();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
nvim.stderr.on("data", (data) => {
|
|
145
|
+
stderr += data.toString();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
nvim.on("close", (code) => {
|
|
149
|
+
const combined = `${stdout}\n${stderr}`;
|
|
150
|
+
let payload = null;
|
|
151
|
+
try {
|
|
152
|
+
payload = extractPayload(combined);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
reject(
|
|
155
|
+
new Error(
|
|
156
|
+
`Failed to parse config payload (${mode}): ${error.message}`,
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (payload !== null) {
|
|
163
|
+
resolve(payload);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (code !== 0) {
|
|
168
|
+
reject(
|
|
169
|
+
new Error(
|
|
170
|
+
`Neovim config detection failed (${mode}): ${combined.trim()}`,
|
|
171
|
+
),
|
|
172
|
+
);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
reject(new Error(`Config payload not found (${mode}).`));
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function extractPayload(output) {
|
|
182
|
+
const start = output.indexOf(START_MARKER);
|
|
183
|
+
const end = output.indexOf(END_MARKER);
|
|
184
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const json = output.slice(start + START_MARKER.length, end).trim();
|
|
189
|
+
if (!json) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return JSON.parse(json);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function summarizeError(error) {
|
|
197
|
+
if (!error) {
|
|
198
|
+
return "unknown_runtime_error";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const message =
|
|
202
|
+
typeof error.message === "string" && error.message.trim()
|
|
203
|
+
? error.message.trim()
|
|
204
|
+
: String(error);
|
|
205
|
+
|
|
206
|
+
return message.split("\n")[0];
|
|
207
|
+
}
|
package/src/detector.js
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
// intent detection pipeline
|
|
2
|
+
|
|
3
|
+
const COMMAND_INTENTS = [
|
|
4
|
+
{ pattern: /^:?Ex!?$/, intent: "navigation.file_explorer" },
|
|
5
|
+
{ pattern: /^:?Git!?$/, intent: "git.fugitive" },
|
|
6
|
+
{ pattern: /^:?G!?$/, intent: "git.fugitive" },
|
|
7
|
+
{ pattern: /^:?NvimTreeToggle!?$/, intent: "plugin.nvim_tree" },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const RAW_PATTERN_INTENTS = [
|
|
11
|
+
{ pattern: /vim\.lsp\.buf\.definition/, intent: "lsp.definition" },
|
|
12
|
+
{ pattern: /vim\.lsp\.buf\.declaration/, intent: "lsp.declaration" },
|
|
13
|
+
{ pattern: /vim\.lsp\.buf\.implementation/, intent: "lsp.implementation" },
|
|
14
|
+
{ pattern: /vim\.lsp\.buf\.references/, intent: "lsp.references" },
|
|
15
|
+
{ pattern: /vim\.lsp\.buf\.hover/, intent: "lsp.hover" },
|
|
16
|
+
{ pattern: /vim\.lsp\.buf\.rename/, intent: "lsp.rename" },
|
|
17
|
+
{ pattern: /vim\.lsp\.buf\.code_action/, intent: "lsp.code_action" },
|
|
18
|
+
{ pattern: /vim\.lsp\.buf\.format/, intent: "lsp.format" },
|
|
19
|
+
{ pattern: /vim\.lsp\.buf\.signature_help/, intent: "lsp.signature_help" },
|
|
20
|
+
{ pattern: /vim\.lsp\.buf\.type_definition/, intent: "lsp.type_definition" },
|
|
21
|
+
{
|
|
22
|
+
pattern: /vim\.lsp\.buf\.add_workspace_folder/,
|
|
23
|
+
intent: "lsp.add_workspace_folder",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
pattern: /vim\.lsp\.buf\.remove_workspace_folder/,
|
|
27
|
+
intent: "lsp.remove_workspace_folder",
|
|
28
|
+
},
|
|
29
|
+
{ pattern: /vim\.diagnostic\.goto_next/, intent: "lsp.diagnostic_next" },
|
|
30
|
+
{ pattern: /vim\.diagnostic\.goto_prev/, intent: "lsp.diagnostic_prev" },
|
|
31
|
+
{
|
|
32
|
+
pattern: /vim\.diagnostic\.setloclist/,
|
|
33
|
+
intent: "lsp.diagnostic_setloclist",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
pattern: /vim\.cmd\.Git\b(?!.*\b(push|pull|commit|add|status|blame)\b)/,
|
|
37
|
+
intent: "git.fugitive",
|
|
38
|
+
},
|
|
39
|
+
{ pattern: /vim\.cmd\.Git.*\bpush\b/i, intent: "git.push" },
|
|
40
|
+
{ pattern: /vim\.cmd\.Git.*\bpull\b/i, intent: "git.pull" },
|
|
41
|
+
{ pattern: /vim\.cmd\.Git.*\bcommit\b/i, intent: "git.commit" },
|
|
42
|
+
{ pattern: /vim\.cmd\.Git.*\badd\b/i, intent: "git.add" },
|
|
43
|
+
{ pattern: /vim\.cmd\.Git.*\bstatus\b/i, intent: "git.status" },
|
|
44
|
+
{ pattern: /vim\.cmd\.Git.*\bblame\b/i, intent: "git.blame" },
|
|
45
|
+
{
|
|
46
|
+
pattern: /telescope\.builtin\.find_files/,
|
|
47
|
+
intent: "navigation.find_files",
|
|
48
|
+
},
|
|
49
|
+
{ pattern: /telescope\.builtin\.live_grep/, intent: "navigation.live_grep" },
|
|
50
|
+
{ pattern: /telescope\.builtin\.buffers/, intent: "navigation.buffers" },
|
|
51
|
+
{
|
|
52
|
+
pattern: /telescope\.builtin\.oldfiles/,
|
|
53
|
+
intent: "navigation.recent_files",
|
|
54
|
+
},
|
|
55
|
+
{ pattern: /telescope\.builtin\.help_tags/, intent: "navigation.help_tags" },
|
|
56
|
+
{
|
|
57
|
+
pattern: /telescope\.builtin\.grep_string/,
|
|
58
|
+
intent: "navigation.grep_string",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
pattern: /telescope\.builtin\.current_buffer_fuzzy_find/,
|
|
62
|
+
intent: "navigation.buffer_search",
|
|
63
|
+
},
|
|
64
|
+
{ pattern: /telescope\.builtin\.resume/, intent: "navigation.resume_search" },
|
|
65
|
+
{
|
|
66
|
+
pattern: /telescope\.builtin\.diagnostics/,
|
|
67
|
+
intent: "navigation.diagnostics",
|
|
68
|
+
},
|
|
69
|
+
{ pattern: /telescope\.builtin\.keymaps/, intent: "navigation.keymaps" },
|
|
70
|
+
{
|
|
71
|
+
pattern: /telescope\.builtin\.command_history/,
|
|
72
|
+
intent: "navigation.command_history",
|
|
73
|
+
},
|
|
74
|
+
{ pattern: /harpoon:list\(\):add\(\)/, intent: "harpoon.add" },
|
|
75
|
+
{ pattern: /harpoon:list\(\):select\(1\)/, intent: "harpoon.select_1" },
|
|
76
|
+
{ pattern: /harpoon:list\(\):select\(2\)/, intent: "harpoon.select_2" },
|
|
77
|
+
{ pattern: /harpoon:list\(\):select\(3\)/, intent: "harpoon.select_3" },
|
|
78
|
+
{ pattern: /harpoon:list\(\):select\(4\)/, intent: "harpoon.select_4" },
|
|
79
|
+
{ pattern: /harpoon:list\(\):prev\(\)/, intent: "harpoon.prev" },
|
|
80
|
+
{ pattern: /harpoon:list\(\):next\(\)/, intent: "harpoon.next" },
|
|
81
|
+
{ pattern: /harpoon\.ui:toggle_quick_menu/, intent: "harpoon.menu" },
|
|
82
|
+
{ pattern: /todo-comments\.jump_next/, intent: "todo.next" },
|
|
83
|
+
{ pattern: /todo-comments\.jump_prev/, intent: "todo.prev" },
|
|
84
|
+
{ pattern: /vim\.cmd\.Ex\b/, intent: "navigation.file_explorer" },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const DESC_ALIAS = {
|
|
88
|
+
find_files: "navigation.find_files",
|
|
89
|
+
switch_buffer: "navigation.buffers",
|
|
90
|
+
buffers: "navigation.buffers",
|
|
91
|
+
recent_files: "navigation.recent_files",
|
|
92
|
+
live_grep: "navigation.live_grep",
|
|
93
|
+
grep_string: "navigation.grep_string",
|
|
94
|
+
go_to_definition: "lsp.definition",
|
|
95
|
+
find_references: "lsp.references",
|
|
96
|
+
hover: "lsp.hover",
|
|
97
|
+
rename: "lsp.rename",
|
|
98
|
+
code_action: "lsp.code_action",
|
|
99
|
+
quick_fix: "lsp.code_action",
|
|
100
|
+
format: "editing.format",
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export function detectIntent(keymap = {}) {
|
|
104
|
+
const rhs = typeof keymap.rhs === "string" ? keymap.rhs : "";
|
|
105
|
+
const rhsSource =
|
|
106
|
+
typeof keymap.rhs_source === "string" ? keymap.rhs_source : "";
|
|
107
|
+
const rhsName = typeof keymap.rhs_name === "string" ? keymap.rhs_name : "";
|
|
108
|
+
const desc = typeof keymap.desc === "string" ? keymap.desc : "";
|
|
109
|
+
const lhs = typeof keymap.lhs === "string" ? keymap.lhs : "";
|
|
110
|
+
const rawText = `${rhs} ${rhsSource} ${rhsName} ${desc}`.trim();
|
|
111
|
+
|
|
112
|
+
const commandIntent = detectCommandIntent(rhs);
|
|
113
|
+
if (commandIntent) {
|
|
114
|
+
return result(
|
|
115
|
+
commandIntent,
|
|
116
|
+
"high",
|
|
117
|
+
true,
|
|
118
|
+
categorizeIntent(commandIntent, rhsSource, desc),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const sourceIntent = detectSourceIntent({ lhs, rhsSource, desc });
|
|
123
|
+
if (sourceIntent) {
|
|
124
|
+
return result(
|
|
125
|
+
sourceIntent,
|
|
126
|
+
"high",
|
|
127
|
+
true,
|
|
128
|
+
categorizeIntent(sourceIntent, rhsSource, desc),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const patternIntent = detectPatternIntent(rawText);
|
|
133
|
+
if (patternIntent) {
|
|
134
|
+
return result(patternIntent, "high", true);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const descIntent = detectDescIntent(desc);
|
|
138
|
+
if (descIntent) {
|
|
139
|
+
return result(
|
|
140
|
+
descIntent,
|
|
141
|
+
"medium",
|
|
142
|
+
true,
|
|
143
|
+
categorizeIntent(descIntent, rhsSource, desc),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result(null, "low", false, "unknown");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function detectIntents(keymaps = []) {
|
|
151
|
+
return keymaps.map((keymap) => {
|
|
152
|
+
const { intent, confidence, translatable, category } = detectIntent(keymap);
|
|
153
|
+
return {
|
|
154
|
+
mode: keymap.mode ?? "n",
|
|
155
|
+
lhs: keymap.lhs ?? "",
|
|
156
|
+
raw_rhs: keymap.rhs ?? "<Lua function>",
|
|
157
|
+
intent,
|
|
158
|
+
confidence,
|
|
159
|
+
translatable,
|
|
160
|
+
desc: keymap.desc ?? null,
|
|
161
|
+
category,
|
|
162
|
+
opts: {
|
|
163
|
+
silent: keymap.silent ?? false,
|
|
164
|
+
noremap: keymap.noremap ?? true,
|
|
165
|
+
buffer: keymap.buffer ?? false,
|
|
166
|
+
nowait: keymap.nowait ?? false,
|
|
167
|
+
expr: keymap.expr ?? false,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function detectCommandIntent(rhs) {
|
|
174
|
+
if (!rhs) return null;
|
|
175
|
+
const match =
|
|
176
|
+
rhs.match(/<cmd>\s*([^<\r\n]+)\s*<CR>/i) ||
|
|
177
|
+
rhs.match(/:\s*([^<\r\n]+)\s*<CR>/i);
|
|
178
|
+
if (!match) return null;
|
|
179
|
+
|
|
180
|
+
const command = match[1].trim();
|
|
181
|
+
for (const item of COMMAND_INTENTS) {
|
|
182
|
+
if (item.pattern.test(command)) {
|
|
183
|
+
return item.intent;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function detectPatternIntent(rawText) {
|
|
190
|
+
if (!rawText) return null;
|
|
191
|
+
for (const item of RAW_PATTERN_INTENTS) {
|
|
192
|
+
if (item.pattern.test(rawText)) {
|
|
193
|
+
return item.intent;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function detectDescIntent(desc) {
|
|
200
|
+
if (!desc) return null;
|
|
201
|
+
const normalized = normalizeLabel(desc);
|
|
202
|
+
const direct = DESC_ALIAS[normalized];
|
|
203
|
+
if (direct) {
|
|
204
|
+
return direct;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const fuzzy = detectFuzzyDescIntent(desc);
|
|
208
|
+
if (fuzzy) {
|
|
209
|
+
return fuzzy;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function normalizeLabel(input) {
|
|
216
|
+
return input
|
|
217
|
+
.toLowerCase()
|
|
218
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
219
|
+
.replace(/^_+|_+$/g, "")
|
|
220
|
+
.replace(/_+/g, "_");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function result(intent, confidence, translatable, category) {
|
|
224
|
+
return { intent, confidence, translatable, category: category ?? "unknown" };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function detectSourceIntent({ lhs, rhsSource, desc }) {
|
|
228
|
+
const source = (rhsSource || "").toLowerCase();
|
|
229
|
+
const key = lhs || "";
|
|
230
|
+
|
|
231
|
+
// detect common keymaps by lhs regardless of source
|
|
232
|
+
const commonKeymaps = {
|
|
233
|
+
"<leader>pv": "navigation.file_explorer",
|
|
234
|
+
"<leader>G": "git.fugitive",
|
|
235
|
+
"<leader>gp": "git.push",
|
|
236
|
+
"<leader>gP": "git.pull",
|
|
237
|
+
"<leader>gac": "git.add",
|
|
238
|
+
"<leader>gc": "git.commit",
|
|
239
|
+
"<leader>gs": "git.status",
|
|
240
|
+
};
|
|
241
|
+
if (commonKeymaps[key]) {
|
|
242
|
+
return commonKeymaps[key];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (source.includes("/core/plugins/harpoon.lua")) {
|
|
246
|
+
return detectHarpoonByLhs(key);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (source.includes("/core/keymaps.lua")) {
|
|
250
|
+
return detectCoreKeymapsByLhs(key) || detectDescIntent(desc);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (source.includes("/telescope/builtin/init.lua")) {
|
|
254
|
+
return detectDescIntent(desc);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (source.includes("/vim/lsp/buf.lua") && key === "<leader>f") {
|
|
258
|
+
return "lsp.format";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function detectCoreKeymapsByLhs(lhs) {
|
|
265
|
+
const table = {
|
|
266
|
+
"<leader>pv": "navigation.file_explorer",
|
|
267
|
+
"<leader>G": "git.fugitive",
|
|
268
|
+
"<leader>gp": "git.push",
|
|
269
|
+
"<leader>gP": "git.pull",
|
|
270
|
+
"<leader>gac": "git.add",
|
|
271
|
+
"<leader>gc": "git.commit",
|
|
272
|
+
"<leader>gs": "git.status",
|
|
273
|
+
"]t": "todo.next",
|
|
274
|
+
"[t": "todo.prev",
|
|
275
|
+
};
|
|
276
|
+
return table[lhs] ?? null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function detectHarpoonByLhs(lhs) {
|
|
280
|
+
const table = {
|
|
281
|
+
"<leader>a": "harpoon.add",
|
|
282
|
+
"<leader>m": "harpoon.menu",
|
|
283
|
+
"<C-h>": "harpoon.select_1",
|
|
284
|
+
"<C-j>": "harpoon.select_2",
|
|
285
|
+
"<C-k>": "harpoon.select_3",
|
|
286
|
+
"<C-l>": "harpoon.select_4",
|
|
287
|
+
"<C-a>": "harpoon.prev",
|
|
288
|
+
"<C-s>": "harpoon.next",
|
|
289
|
+
};
|
|
290
|
+
return table[lhs] ?? null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function detectFuzzyDescIntent(desc) {
|
|
294
|
+
const text = desc.toLowerCase().trim();
|
|
295
|
+
if (!text) return null;
|
|
296
|
+
|
|
297
|
+
const rules = [
|
|
298
|
+
{ pattern: /\b(find files?|file find)\b/, intent: "navigation.find_files" },
|
|
299
|
+
{
|
|
300
|
+
pattern: /\b(switch buffer|buffer list|buffers)\b/,
|
|
301
|
+
intent: "navigation.buffers",
|
|
302
|
+
},
|
|
303
|
+
{ pattern: /\b(recent files?)\b/, intent: "navigation.recent_files" },
|
|
304
|
+
{
|
|
305
|
+
pattern: /\b(live grep|grep \(root dir\)|grep)\b/,
|
|
306
|
+
intent: "navigation.live_grep",
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
pattern: /\b(search buffer|buffer search)\b/,
|
|
310
|
+
intent: "navigation.buffer_search",
|
|
311
|
+
},
|
|
312
|
+
{ pattern: /\b(help pages?|help tags?)\b/, intent: "navigation.help_tags" },
|
|
313
|
+
{ pattern: /\b(key maps?|keymaps?)\b/, intent: "navigation.keymaps" },
|
|
314
|
+
{ pattern: /\b(command history)\b/, intent: "navigation.command_history" },
|
|
315
|
+
{
|
|
316
|
+
pattern: /\b(word under cursor|grep string)\b/,
|
|
317
|
+
intent: "navigation.grep_string",
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
pattern:
|
|
321
|
+
/\b(document diagnostics?|workspace diagnostics?|diagnostics?)\b/,
|
|
322
|
+
intent: "navigation.diagnostics",
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
pattern: /\b(resume( last)? search)\b/,
|
|
326
|
+
intent: "navigation.resume_search",
|
|
327
|
+
},
|
|
328
|
+
{ pattern: /\b(next todo comment|todo next)\b/, intent: "todo.next" },
|
|
329
|
+
{
|
|
330
|
+
pattern: /\b(previous todo comment|todo prev|todo previous)\b/,
|
|
331
|
+
intent: "todo.prev",
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
pattern: /\b(go to definition|goto definition)\b/,
|
|
335
|
+
intent: "lsp.definition",
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
pattern: /\b(find references|go to references)\b/,
|
|
339
|
+
intent: "lsp.references",
|
|
340
|
+
},
|
|
341
|
+
{ pattern: /^(hover|show hover|lsp hover)$/, intent: "lsp.hover" },
|
|
342
|
+
{ pattern: /^(rename|rename symbol|lsp rename)$/, intent: "lsp.rename" },
|
|
343
|
+
{
|
|
344
|
+
pattern: /^(code action|quick fix|lsp code action)$/,
|
|
345
|
+
intent: "lsp.code_action",
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
pattern: /^(format|format document|lsp format)$/,
|
|
349
|
+
intent: "editing.format",
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
for (const rule of rules) {
|
|
354
|
+
if (rule.pattern.test(text)) {
|
|
355
|
+
return rule.intent;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function categorizeIntent(intent, rhsSource, desc) {
|
|
363
|
+
if (!intent) {
|
|
364
|
+
return "unknown";
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (
|
|
368
|
+
intent.startsWith("harpoon.") ||
|
|
369
|
+
intent.startsWith("todo.") ||
|
|
370
|
+
intent.startsWith("plugin.")
|
|
371
|
+
) {
|
|
372
|
+
return "plugin";
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (intent.startsWith("lsp.")) {
|
|
376
|
+
return "lsp";
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (intent.startsWith("git.")) {
|
|
380
|
+
return "git";
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (intent.startsWith("navigation.")) {
|
|
384
|
+
if (isTodoDesc(desc) || rhsSource.includes("todo-comments")) {
|
|
385
|
+
return "plugin";
|
|
386
|
+
}
|
|
387
|
+
return "navigation";
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (intent.startsWith("editing.")) {
|
|
391
|
+
return "editing";
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return "unknown";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function isTodoDesc(desc) {
|
|
398
|
+
if (!desc) return false;
|
|
399
|
+
const lower = desc.toLowerCase();
|
|
400
|
+
return lower.includes("todo") || lower.includes("comment");
|
|
401
|
+
}
|