-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconvert-sublime-completions.mjs
More file actions
151 lines (124 loc) · 4.97 KB
/
Copy pathconvert-sublime-completions.mjs
File metadata and controls
151 lines (124 loc) · 4.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/usr/bin/env node
// convert-sublime-completions.mjs
// Converts all .sublime-completions files kit/completions/
// into src/snippets.ts format
import { readdirSync, readFileSync, writeFileSync } from "fs";
import { join, basename } from "path";
const COMPLETIONS_DIR = "./kit/completions";
const SNIPPETS_DIR = "./kit/snippets";
const OUTPUT_FILE = "./src/snippets.ts";
const FILE_TYPES = ["pwn", "inc"];
// Use the filename (annotation) directly as the category name
function getCategory(annotation) {
return annotation || "Pawn";
}
// Convert sublime ${1:param} → same (already compatible with ACE)
// Only simple triggers without contents → just the trigger word (constant/define)
function buildSnippet(trigger, contents) {
if (!contents) return trigger;
// Sublime uses ${1:text} — compatible with ACE snippet format already
return contents;
}
// Escape string for JS template literal / JSON string
function escapeStr(s) {
return s
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/\n/g, "\\n")
.replace(/\r/g, "")
.replace(/\t/g, "\\t");
}
// Read all completions
const snippets = [];
const seen = new Set();
// ── 1. Read .sublime-completions files ──────────────────────────────
const files = readdirSync(COMPLETIONS_DIR).filter(f => f.endsWith(".sublime-completions"));
for (const file of files) {
const annotation = basename(file, ".sublime-completions");
const category = getCategory(annotation);
const raw = readFileSync(join(COMPLETIONS_DIR, file), "utf-8");
let data;
try {
// Strip block comments /* ... */ and line comments // ...
const cleaned = raw
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/\/\/[^\n]*/g, "")
// Remove trailing commas before ] or }
.replace(/,(\s*[\]}])/g, "$1");
data = JSON.parse(cleaned);
} catch (e) {
console.warn(`⚠ Failed to parse ${file}: ${e.message}`);
continue;
}
for (const c of (data.completions || [])) {
const trigger = c.trigger;
const contents = c.contents || null;
if (!trigger) continue;
// Skip duplicates
const key = trigger;
if (seen.has(key)) continue;
seen.add(key);
const snippet = buildSnippet(trigger, contents);
snippets.push({
prefix: trigger,
snippet,
type: category,
description: `<code>${escapeStr(trigger)}</code> — ${annotation}`,
fileTypes: FILE_TYPES,
_isConstant: !contents, // flag: no tab stops
});
}
}
// ── 2. Read .sublime-snippet files ──────────────────────────────────
const snippetFiles = readdirSync(SNIPPETS_DIR).filter(f => f.endsWith(".sublime-snippet"));
for (const file of snippetFiles) {
const raw = readFileSync(join(SNIPPETS_DIR, file), "utf-8");
// Extract fields from XML
const descMatch = raw.match(/<description>(.*?)<\/description>/s);
const contentMatch = raw.match(/<!\[CDATA\[([\s\S]*?)\]\]>/);
const triggerMatch = raw.match(/<tabTrigger>(.*?)<\/tabTrigger>/);
if (!contentMatch || !triggerMatch) continue;
const trigger = triggerMatch[1].trim();
const content = contentMatch[1];
const description = descMatch ? descMatch[1].trim() : trigger;
const category = "Snippet";
if (seen.has(trigger)) continue;
seen.add(trigger);
snippets.push({
prefix: trigger,
snippet: content.trim(),
type: category,
description,
fileTypes: FILE_TYPES,
_isConstant: false,
});
}
console.log(`✓ Total snippets collected: ${snippets.length}`);
// ── 3. Write snippets.ts ─────────────────────────────────────────────
// Separate: constants (no tab stops) vs function natives (has tab stops)
// Constants will be simple: prefix = snippet (just a word)
const lines = [];
lines.push(`export interface Snippet {`);
lines.push(` prefix: string;`);
lines.push(` snippet: string;`);
lines.push(` type: string;`);
lines.push(` description?: string;`);
lines.push(` fileTypes: string[];`);
lines.push(`}`);
lines.push(``);
lines.push(`// Auto-generated from sublime-kit`);
lines.push(`// Total: ${snippets.length} snippets`);
lines.push(`export const snippets: Snippet[] = [`);
for (const s of snippets) {
const desc = s.description ? `\n description: "${escapeStr(s.description)}",` : "";
lines.push(` {`);
lines.push(` prefix: "${escapeStr(s.prefix)}",`);
lines.push(` snippet: "${escapeStr(s.snippet)}",`);
lines.push(` type: "${s.type}",${desc}`);
lines.push(` fileTypes: ["pwn", "inc"],`);
lines.push(` },`);
}
lines.push(`];`);
lines.push(``);
writeFileSync(OUTPUT_FILE, lines.join("\n"), "utf-8");
console.log(`✓ Written to ${OUTPUT_FILE}`);