@rohal12/spindle-lsp 0.3.5 → 0.5.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/dist/bin.js +577 -50
- package/dist/bin.js.map +4 -4
- package/package.json +12 -6
package/dist/bin.js
CHANGED
|
@@ -1011,6 +1011,12 @@ var init_macro_supplements = __esm({
|
|
|
1011
1011
|
name: "unwatch",
|
|
1012
1012
|
description: "Stops watching a variable.\n\nUsage: `{unwatch $var}`",
|
|
1013
1013
|
parameters: ["...text"]
|
|
1014
|
+
},
|
|
1015
|
+
unless: {
|
|
1016
|
+
name: "unless",
|
|
1017
|
+
container: true,
|
|
1018
|
+
description: "Conditionally displays its contents when the expression is falsy. The inverse of `{if}`.\n\nUsage:\n\n```\n{unless condition}\n ...\n{/unless}\n```",
|
|
1019
|
+
parameters: ["...text"]
|
|
1014
1020
|
}
|
|
1015
1021
|
};
|
|
1016
1022
|
}
|
|
@@ -2787,51 +2793,369 @@ var init_check = __esm({
|
|
|
2787
2793
|
}
|
|
2788
2794
|
});
|
|
2789
2795
|
|
|
2790
|
-
// src/plugins/format.ts
|
|
2791
|
-
function
|
|
2796
|
+
// src/plugins/format/segment.ts
|
|
2797
|
+
function splitPassages(text) {
|
|
2792
2798
|
const lines = text.split("\n");
|
|
2793
|
-
|
|
2794
|
-
|
|
2799
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
2800
|
+
lines.pop();
|
|
2801
|
+
}
|
|
2802
|
+
const passages = [];
|
|
2803
|
+
let currentHeader = "";
|
|
2804
|
+
let bodyLines = [];
|
|
2805
|
+
let startLine = 0;
|
|
2806
|
+
function pushPassage() {
|
|
2807
|
+
if (currentHeader || bodyLines.length > 0) {
|
|
2808
|
+
while (bodyLines.length > 0 && bodyLines[bodyLines.length - 1].trim() === "") {
|
|
2809
|
+
bodyLines.pop();
|
|
2810
|
+
}
|
|
2811
|
+
passages.push({
|
|
2812
|
+
header: currentHeader,
|
|
2813
|
+
body: bodyLines.join("\n") + (bodyLines.length > 0 ? "\n" : ""),
|
|
2814
|
+
startLine
|
|
2815
|
+
});
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2795
2818
|
for (let i = 0; i < lines.length; i++) {
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2819
|
+
if (PASSAGE_HEADER_REGEX.test(lines[i])) {
|
|
2820
|
+
pushPassage();
|
|
2821
|
+
currentHeader = lines[i];
|
|
2822
|
+
bodyLines = [];
|
|
2823
|
+
startLine = i;
|
|
2824
|
+
} else {
|
|
2825
|
+
bodyLines.push(lines[i]);
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
pushPassage();
|
|
2829
|
+
return passages;
|
|
2830
|
+
}
|
|
2831
|
+
function classifyPassage(header) {
|
|
2832
|
+
const tagMatch = header.match(/\[([^\]]*)\]/g);
|
|
2833
|
+
if (!tagMatch) return "normal";
|
|
2834
|
+
const tags = tagMatch.map((t) => t.slice(1, -1).trim().toLowerCase());
|
|
2835
|
+
if (tags.includes("script")) return "script";
|
|
2836
|
+
if (tags.includes("stylesheet")) return "stylesheet";
|
|
2837
|
+
return "normal";
|
|
2838
|
+
}
|
|
2839
|
+
function segmentRegions(body) {
|
|
2840
|
+
const lines = body.split("\n");
|
|
2841
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
2842
|
+
lines.pop();
|
|
2843
|
+
}
|
|
2844
|
+
if (lines.length === 0) return [];
|
|
2845
|
+
const regions = [];
|
|
2846
|
+
let i = 0;
|
|
2847
|
+
while (i < lines.length) {
|
|
2848
|
+
const line = lines[i];
|
|
2849
|
+
const trimmed = line.trim();
|
|
2850
|
+
if (/^<script(\s|>)/i.test(trimmed)) {
|
|
2851
|
+
const scriptLines = [line];
|
|
2852
|
+
i++;
|
|
2853
|
+
while (i < lines.length && !/<\/script>/i.test(lines[i])) {
|
|
2854
|
+
scriptLines.push(lines[i]);
|
|
2855
|
+
i++;
|
|
2856
|
+
}
|
|
2857
|
+
if (i < lines.length) {
|
|
2858
|
+
scriptLines.push(lines[i]);
|
|
2859
|
+
i++;
|
|
2860
|
+
}
|
|
2861
|
+
regions.push({ type: "script", lines: scriptLines });
|
|
2862
|
+
continue;
|
|
2863
|
+
}
|
|
2864
|
+
if (/^<([a-zA-Z][\w-]*)/.test(trimmed)) {
|
|
2865
|
+
const tagMatch = trimmed.match(/^<([a-zA-Z][\w-]*)/);
|
|
2866
|
+
const tagName = tagMatch[1].toLowerCase();
|
|
2867
|
+
if (VOID_ELEMENTS.has(tagName) && !lines[i + 1]?.trim().startsWith("<")) {
|
|
2868
|
+
pushSpindleLine(regions, line);
|
|
2869
|
+
i++;
|
|
2870
|
+
continue;
|
|
2871
|
+
}
|
|
2872
|
+
const selfClosing = new RegExp(`</${tagName}\\s*>`, "i");
|
|
2873
|
+
if (selfClosing.test(trimmed)) {
|
|
2874
|
+
pushSpindleLine(regions, line);
|
|
2875
|
+
i++;
|
|
2876
|
+
continue;
|
|
2877
|
+
}
|
|
2878
|
+
const htmlLines = [line];
|
|
2879
|
+
let depth = 1;
|
|
2880
|
+
i++;
|
|
2881
|
+
while (i < lines.length && depth > 0) {
|
|
2882
|
+
htmlLines.push(lines[i]);
|
|
2883
|
+
const opens = (lines[i].match(new RegExp(`<${tagName}[\\s>]`, "gi")) ?? []).length;
|
|
2884
|
+
const closes = (lines[i].match(new RegExp(`</${tagName}\\s*>`, "gi")) ?? []).length;
|
|
2885
|
+
depth += opens - closes;
|
|
2886
|
+
i++;
|
|
2887
|
+
}
|
|
2888
|
+
regions.push({ type: "html", lines: htmlLines });
|
|
2802
2889
|
continue;
|
|
2803
2890
|
}
|
|
2891
|
+
pushSpindleLine(regions, line);
|
|
2892
|
+
i++;
|
|
2893
|
+
}
|
|
2894
|
+
return regions;
|
|
2895
|
+
}
|
|
2896
|
+
function pushSpindleLine(regions, line) {
|
|
2897
|
+
const last = regions[regions.length - 1];
|
|
2898
|
+
if (last?.type === "spindle") {
|
|
2899
|
+
last.lines.push(line);
|
|
2900
|
+
} else {
|
|
2901
|
+
regions.push({ type: "spindle", lines: [line] });
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
var PASSAGE_HEADER_REGEX, VOID_ELEMENTS;
|
|
2905
|
+
var init_segment = __esm({
|
|
2906
|
+
"src/plugins/format/segment.ts"() {
|
|
2907
|
+
"use strict";
|
|
2908
|
+
PASSAGE_HEADER_REGEX = /^::\s+/;
|
|
2909
|
+
VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
2910
|
+
"area",
|
|
2911
|
+
"base",
|
|
2912
|
+
"br",
|
|
2913
|
+
"col",
|
|
2914
|
+
"embed",
|
|
2915
|
+
"hr",
|
|
2916
|
+
"img",
|
|
2917
|
+
"input",
|
|
2918
|
+
"link",
|
|
2919
|
+
"meta",
|
|
2920
|
+
"param",
|
|
2921
|
+
"source",
|
|
2922
|
+
"track",
|
|
2923
|
+
"wbr"
|
|
2924
|
+
]);
|
|
2925
|
+
}
|
|
2926
|
+
});
|
|
2927
|
+
|
|
2928
|
+
// src/plugins/format/prettier-bridge.ts
|
|
2929
|
+
async function loadPrettier() {
|
|
2930
|
+
if (prettierLoaded) return prettier;
|
|
2931
|
+
prettierLoaded = true;
|
|
2932
|
+
try {
|
|
2933
|
+
prettier = await import("prettier");
|
|
2934
|
+
} catch {
|
|
2935
|
+
prettier = null;
|
|
2936
|
+
}
|
|
2937
|
+
return prettier;
|
|
2938
|
+
}
|
|
2939
|
+
async function getBaseConfig() {
|
|
2940
|
+
const p = await loadPrettier();
|
|
2941
|
+
if (!p) return {};
|
|
2942
|
+
try {
|
|
2943
|
+
const resolved = await p.resolveConfig(process.cwd());
|
|
2944
|
+
return { tabWidth: 2, ...resolved ?? {} };
|
|
2945
|
+
} catch {
|
|
2946
|
+
return { tabWidth: 2 };
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
async function formatJS(code) {
|
|
2950
|
+
const p = await loadPrettier();
|
|
2951
|
+
if (!p) return code;
|
|
2952
|
+
try {
|
|
2953
|
+
const config = await getBaseConfig();
|
|
2954
|
+
return await p.format(code, { ...config, parser: "babel" });
|
|
2955
|
+
} catch {
|
|
2956
|
+
return code;
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
async function formatCSS(code) {
|
|
2960
|
+
const p = await loadPrettier();
|
|
2961
|
+
if (!p) return code;
|
|
2962
|
+
try {
|
|
2963
|
+
const config = await getBaseConfig();
|
|
2964
|
+
return await p.format(code, { ...config, parser: "css" });
|
|
2965
|
+
} catch {
|
|
2966
|
+
return code;
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
async function formatHTML(code) {
|
|
2970
|
+
const p = await loadPrettier();
|
|
2971
|
+
if (!p) return code;
|
|
2972
|
+
try {
|
|
2973
|
+
const config = await getBaseConfig();
|
|
2974
|
+
return await p.format(code, {
|
|
2975
|
+
...config,
|
|
2976
|
+
parser: "html",
|
|
2977
|
+
htmlWhitespaceSensitivity: "ignore"
|
|
2978
|
+
});
|
|
2979
|
+
} catch {
|
|
2980
|
+
return code;
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
var prettier, prettierLoaded;
|
|
2984
|
+
var init_prettier_bridge = __esm({
|
|
2985
|
+
"src/plugins/format/prettier-bridge.ts"() {
|
|
2986
|
+
"use strict";
|
|
2987
|
+
prettier = null;
|
|
2988
|
+
prettierLoaded = false;
|
|
2989
|
+
}
|
|
2990
|
+
});
|
|
2991
|
+
|
|
2992
|
+
// src/plugins/format/placeholders.ts
|
|
2993
|
+
function isInsideAttribute(text, pos) {
|
|
2994
|
+
let inQuote = false;
|
|
2995
|
+
let quoteChar = "";
|
|
2996
|
+
for (let i = 0; i < pos; i++) {
|
|
2997
|
+
const ch = text[i];
|
|
2998
|
+
if (!inQuote && (ch === '"' || ch === "'") && i > 0 && text[i - 1] === "=") {
|
|
2999
|
+
inQuote = true;
|
|
3000
|
+
quoteChar = ch;
|
|
3001
|
+
} else if (inQuote && ch === quoteChar) {
|
|
3002
|
+
inQuote = false;
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
return inQuote;
|
|
3006
|
+
}
|
|
3007
|
+
function replaceSpindleTokens(html) {
|
|
3008
|
+
const tokens = [];
|
|
3009
|
+
const text = html.replace(SPINDLE_TOKEN_REGEX, (match, ...args2) => {
|
|
3010
|
+
const offset = args2[args2.length - 2];
|
|
3011
|
+
const idx = tokens.length;
|
|
3012
|
+
tokens.push(match);
|
|
3013
|
+
if (isInsideAttribute(html, offset)) {
|
|
3014
|
+
return `__SP${idx}__`;
|
|
3015
|
+
}
|
|
3016
|
+
return `<!--SP:${idx}-->`;
|
|
3017
|
+
});
|
|
3018
|
+
return { text, tokens };
|
|
3019
|
+
}
|
|
3020
|
+
function restoreSpindleTokens(text, tokens) {
|
|
3021
|
+
let result = text;
|
|
3022
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
3023
|
+
result = result.replace(`<!--SP:${i}-->`, tokens[i]);
|
|
3024
|
+
result = result.replace(`__SP${i}__`, tokens[i]);
|
|
3025
|
+
}
|
|
3026
|
+
return result;
|
|
3027
|
+
}
|
|
3028
|
+
var SPINDLE_TOKEN_REGEX;
|
|
3029
|
+
var init_placeholders = __esm({
|
|
3030
|
+
"src/plugins/format/placeholders.ts"() {
|
|
3031
|
+
"use strict";
|
|
3032
|
+
SPINDLE_TOKEN_REGEX = /(?:\{\/[A-Za-z][\w-]*\s*\}|\{(?:[#.][a-zA-Z][\w-]*\s*)*[A-Za-z][\w-]*(?:\s+(?:[^}]|\}(?=[^}]))*?)?\}|\{[$_@][a-zA-Z][\w.]*\}|\[\[(?:[^\]]*)\]\])/g;
|
|
3033
|
+
}
|
|
3034
|
+
});
|
|
3035
|
+
|
|
3036
|
+
// src/plugins/format.ts
|
|
3037
|
+
function getSupplementContainers() {
|
|
3038
|
+
const containers = /* @__PURE__ */ new Set();
|
|
3039
|
+
for (const [key, entry] of Object.entries(macro_supplements_default)) {
|
|
3040
|
+
if (entry.container) {
|
|
3041
|
+
containers.add(key.toLowerCase());
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
return containers;
|
|
3045
|
+
}
|
|
3046
|
+
function detectContainersFromText(text) {
|
|
3047
|
+
const found = /* @__PURE__ */ new Set();
|
|
3048
|
+
const re = /\{\/([A-Za-z][\w-]*)\s*\}/g;
|
|
3049
|
+
let m;
|
|
3050
|
+
while ((m = re.exec(text)) !== null) {
|
|
3051
|
+
found.add(m[1].toLowerCase());
|
|
3052
|
+
}
|
|
3053
|
+
return found;
|
|
3054
|
+
}
|
|
3055
|
+
function buildDefaultIsBlock(text) {
|
|
3056
|
+
const containers = getSupplementContainers();
|
|
3057
|
+
for (const name of detectContainersFromText(text)) {
|
|
3058
|
+
containers.add(name);
|
|
3059
|
+
}
|
|
3060
|
+
return (name) => containers.has(name.toLowerCase());
|
|
3061
|
+
}
|
|
3062
|
+
async function formatDocument(text, options) {
|
|
3063
|
+
const isBlock = options?.isBlock ?? buildDefaultIsBlock(text);
|
|
3064
|
+
const isDedenting = options?.isDedentingSubMacro ?? ((name) => DEFAULT_DEDENTING.has(name.toLowerCase()));
|
|
3065
|
+
const passages = splitPassages(text);
|
|
3066
|
+
const resultLines = [];
|
|
3067
|
+
for (let pi = 0; pi < passages.length; pi++) {
|
|
3068
|
+
const passage = passages[pi];
|
|
3069
|
+
if (pi > 0 && passage.header) {
|
|
3070
|
+
const prev = passages[pi - 1];
|
|
3071
|
+
const prevBodyLineCount = prev.body ? prev.body.split("\n").length - 1 : 0;
|
|
3072
|
+
const prevEndLine = prev.startLine + (prev.header ? 1 : 0) + prevBodyLineCount;
|
|
3073
|
+
if (passage.startLine > prevEndLine) {
|
|
3074
|
+
resultLines.push("");
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
if (passage.header) {
|
|
3078
|
+
const header = PASSAGE_HEADER_REGEX2.test(passage.header) ? normalizePassageHeader(passage.header) : passage.header;
|
|
3079
|
+
resultLines.push(header);
|
|
3080
|
+
}
|
|
3081
|
+
const kind = classifyPassage(passage.header);
|
|
3082
|
+
if (kind === "script") {
|
|
3083
|
+
const formatted = await formatJS(passage.body.trim());
|
|
3084
|
+
resultLines.push(formatted.trim());
|
|
3085
|
+
continue;
|
|
3086
|
+
}
|
|
3087
|
+
if (kind === "stylesheet") {
|
|
3088
|
+
const formatted = await formatCSS(passage.body.trim());
|
|
3089
|
+
resultLines.push(formatted.trim());
|
|
3090
|
+
continue;
|
|
3091
|
+
}
|
|
3092
|
+
const regions = segmentRegions(passage.body);
|
|
3093
|
+
for (const region of regions) {
|
|
3094
|
+
if (region.type === "script") {
|
|
3095
|
+
const firstLine = region.lines[0];
|
|
3096
|
+
const lastLine = region.lines[region.lines.length - 1];
|
|
3097
|
+
const innerLines = region.lines.slice(1, region.lines.length - 1);
|
|
3098
|
+
const innerCode = innerLines.join("\n");
|
|
3099
|
+
const formatted = await formatJS(innerCode.trim());
|
|
3100
|
+
resultLines.push(firstLine);
|
|
3101
|
+
if (formatted.trim()) {
|
|
3102
|
+
for (const fLine of formatted.trim().split("\n")) {
|
|
3103
|
+
resultLines.push(" " + fLine);
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
resultLines.push(lastLine);
|
|
3107
|
+
continue;
|
|
3108
|
+
}
|
|
3109
|
+
if (region.type === "html") {
|
|
3110
|
+
const htmlText = region.lines.join("\n");
|
|
3111
|
+
const { text: placeholdered, tokens } = replaceSpindleTokens(htmlText);
|
|
3112
|
+
const formatted = await formatHTML(placeholdered);
|
|
3113
|
+
const restored = restoreSpindleTokens(formatted.trim(), tokens);
|
|
3114
|
+
for (const fLine of restored.split("\n")) {
|
|
3115
|
+
resultLines.push(fLine);
|
|
3116
|
+
}
|
|
3117
|
+
continue;
|
|
3118
|
+
}
|
|
3119
|
+
const indented = indentMacros(region.lines, isBlock, isDedenting);
|
|
3120
|
+
resultLines.push(...indented);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
let output = resultLines.map((l) => l.replace(/\s+$/, "")).join("\n");
|
|
3124
|
+
output = output.replace(/\n*$/, "\n");
|
|
3125
|
+
return output;
|
|
3126
|
+
}
|
|
3127
|
+
function indentMacros(lines, isBlock, isDedenting) {
|
|
3128
|
+
const result = [];
|
|
3129
|
+
let indentLevel = 0;
|
|
3130
|
+
for (const rawLine of lines) {
|
|
3131
|
+
const line = rawLine.replace(/\s+$/, "");
|
|
2804
3132
|
const trimmed = line.trim();
|
|
2805
3133
|
if (trimmed === "") {
|
|
2806
3134
|
result.push("");
|
|
2807
3135
|
continue;
|
|
2808
3136
|
}
|
|
2809
|
-
|
|
3137
|
+
const dedentMatch = trimmed.match(MACRO_OPEN_REGEX);
|
|
3138
|
+
if (dedentMatch && isDedenting(dedentMatch[1])) {
|
|
3139
|
+
indentLevel = Math.max(0, indentLevel - 1);
|
|
3140
|
+
result.push(indentLevel > 0 ? " ".repeat(indentLevel) + trimmed : trimmed);
|
|
3141
|
+
indentLevel++;
|
|
3142
|
+
continue;
|
|
3143
|
+
}
|
|
3144
|
+
const closeMatch = trimmed.match(MACRO_CLOSE_REGEX);
|
|
3145
|
+
if (closeMatch) {
|
|
2810
3146
|
indentLevel = Math.max(0, indentLevel - 1);
|
|
2811
3147
|
}
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
if (
|
|
3148
|
+
result.push(indentLevel > 0 ? " ".repeat(indentLevel) + trimmed : trimmed);
|
|
3149
|
+
const openMatch = trimmed.match(MACRO_OPEN_REGEX);
|
|
3150
|
+
if (openMatch && isBlock(openMatch[1])) {
|
|
2815
3151
|
indentLevel++;
|
|
2816
3152
|
}
|
|
2817
3153
|
}
|
|
2818
|
-
let output = result.join("\n");
|
|
2819
|
-
output = output.replace(/\n*$/, "\n");
|
|
2820
|
-
return output;
|
|
2821
|
-
}
|
|
2822
|
-
function formatRange(text, range) {
|
|
2823
|
-
const lines = text.split("\n");
|
|
2824
|
-
const startLine = Math.max(0, range.start.line);
|
|
2825
|
-
const endLine = Math.min(lines.length - 1, range.end.line);
|
|
2826
|
-
const rangeLines = lines.slice(startLine, endLine + 1);
|
|
2827
|
-
const rangeText = rangeLines.join("\n");
|
|
2828
|
-
const formatted = formatDocument(rangeText);
|
|
2829
|
-
const formattedLines = formatted.replace(/\n$/, "").split("\n");
|
|
2830
|
-
const before = lines.slice(0, startLine);
|
|
2831
|
-
const after = lines.slice(endLine + 1);
|
|
2832
|
-
const result = [...before, ...formattedLines, ...after].join("\n");
|
|
2833
3154
|
return result;
|
|
2834
3155
|
}
|
|
3156
|
+
async function formatRange(text, _range, options) {
|
|
3157
|
+
return formatDocument(text, options);
|
|
3158
|
+
}
|
|
2835
3159
|
function normalizePassageHeader(line) {
|
|
2836
3160
|
const headerMatch = line.match(/^::\s+(.*)/);
|
|
2837
3161
|
if (!headerMatch) return line;
|
|
@@ -2851,13 +3175,18 @@ function normalizePassageHeader(line) {
|
|
|
2851
3175
|
}
|
|
2852
3176
|
return `:: ${name}${suffix}`;
|
|
2853
3177
|
}
|
|
2854
|
-
var
|
|
3178
|
+
var PASSAGE_HEADER_REGEX2, MACRO_OPEN_REGEX, MACRO_CLOSE_REGEX, DEFAULT_DEDENTING, formatPlugin;
|
|
2855
3179
|
var init_format = __esm({
|
|
2856
3180
|
"src/plugins/format.ts"() {
|
|
2857
3181
|
"use strict";
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
3182
|
+
init_macro_supplements();
|
|
3183
|
+
init_segment();
|
|
3184
|
+
init_prettier_bridge();
|
|
3185
|
+
init_placeholders();
|
|
3186
|
+
PASSAGE_HEADER_REGEX2 = /^(::)\s+/;
|
|
3187
|
+
MACRO_OPEN_REGEX = /^\{(?:[#.][a-zA-Z][\w-]*\s*)*([A-Za-z][\w-]*)\b/;
|
|
3188
|
+
MACRO_CLOSE_REGEX = /^\{\/([A-Za-z][\w-]*)\b/;
|
|
3189
|
+
DEFAULT_DEDENTING = /* @__PURE__ */ new Set(["else", "elseif", "next", "case", "default"]);
|
|
2861
3190
|
formatPlugin = {
|
|
2862
3191
|
id: "format",
|
|
2863
3192
|
capabilities: {
|
|
@@ -2865,10 +3194,14 @@ var init_format = __esm({
|
|
|
2865
3194
|
documentRangeFormattingProvider: true
|
|
2866
3195
|
},
|
|
2867
3196
|
initialize(ctx) {
|
|
2868
|
-
|
|
3197
|
+
const formatOpts = {
|
|
3198
|
+
isBlock: (name) => ctx.workspace.macros.isBlock(name),
|
|
3199
|
+
isDedentingSubMacro: (name) => DEFAULT_DEDENTING.has(name.toLowerCase())
|
|
3200
|
+
};
|
|
3201
|
+
ctx.connection.onDocumentFormatting(async (params) => {
|
|
2869
3202
|
const text = ctx.workspace.documents.getText(params.textDocument.uri);
|
|
2870
3203
|
if (text === void 0) return [];
|
|
2871
|
-
const formatted = formatDocument(text);
|
|
3204
|
+
const formatted = await formatDocument(text, formatOpts);
|
|
2872
3205
|
if (formatted === text) return [];
|
|
2873
3206
|
const lines = text.split("\n");
|
|
2874
3207
|
return [{
|
|
@@ -2879,14 +3212,14 @@ var init_format = __esm({
|
|
|
2879
3212
|
newText: formatted
|
|
2880
3213
|
}];
|
|
2881
3214
|
});
|
|
2882
|
-
ctx.connection.onDocumentRangeFormatting((params) => {
|
|
3215
|
+
ctx.connection.onDocumentRangeFormatting(async (params) => {
|
|
2883
3216
|
const text = ctx.workspace.documents.getText(params.textDocument.uri);
|
|
2884
3217
|
if (text === void 0) return [];
|
|
2885
3218
|
const range = {
|
|
2886
3219
|
start: { line: params.range.start.line, character: params.range.start.character },
|
|
2887
3220
|
end: { line: params.range.end.line, character: params.range.end.character }
|
|
2888
3221
|
};
|
|
2889
|
-
const formatted = formatRange(text, range);
|
|
3222
|
+
const formatted = await formatRange(text, range, formatOpts);
|
|
2890
3223
|
if (formatted === text) return [];
|
|
2891
3224
|
const lines = text.split("\n");
|
|
2892
3225
|
return [{
|
|
@@ -2947,7 +3280,7 @@ async function runFormat(args2) {
|
|
|
2947
3280
|
for (const filePath of uniqueFiles) {
|
|
2948
3281
|
try {
|
|
2949
3282
|
const text = readFileSync5(filePath, "utf-8");
|
|
2950
|
-
const formatted = formatDocument(text);
|
|
3283
|
+
const formatted = await formatDocument(text);
|
|
2951
3284
|
if (formatted !== text) {
|
|
2952
3285
|
if (options.check) {
|
|
2953
3286
|
const relative = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
@@ -2979,6 +3312,197 @@ var init_format2 = __esm({
|
|
|
2979
3312
|
}
|
|
2980
3313
|
});
|
|
2981
3314
|
|
|
3315
|
+
// src/mcp/server.ts
|
|
3316
|
+
var server_exports = {};
|
|
3317
|
+
__export(server_exports, {
|
|
3318
|
+
startMcpServer: () => startMcpServer
|
|
3319
|
+
});
|
|
3320
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3321
|
+
import { resolve as resolve2 } from "node:path";
|
|
3322
|
+
import { pathToFileURL as pathToFileURL3, fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3323
|
+
import { glob as glob3 } from "glob";
|
|
3324
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3325
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3326
|
+
import { z } from "zod";
|
|
3327
|
+
function filterBySeverity2(diagnostics, minSeverity) {
|
|
3328
|
+
const minIdx = SEVERITY_ORDER2.indexOf(minSeverity);
|
|
3329
|
+
return diagnostics.filter((d) => {
|
|
3330
|
+
const idx = SEVERITY_ORDER2.indexOf(d.severity);
|
|
3331
|
+
return idx >= minIdx;
|
|
3332
|
+
});
|
|
3333
|
+
}
|
|
3334
|
+
async function resolveFiles(pattern, cwd) {
|
|
3335
|
+
const matches = await glob3(pattern, {
|
|
3336
|
+
cwd,
|
|
3337
|
+
absolute: true,
|
|
3338
|
+
nodir: true
|
|
3339
|
+
});
|
|
3340
|
+
return [...new Set(matches)];
|
|
3341
|
+
}
|
|
3342
|
+
function findConfigRoot(files) {
|
|
3343
|
+
const dirs = files.map((f) => resolve2(f, ".."));
|
|
3344
|
+
let configRoot = dirs.reduce((a, b) => {
|
|
3345
|
+
while (!b.startsWith(a)) a = resolve2(a, "..");
|
|
3346
|
+
return a;
|
|
3347
|
+
});
|
|
3348
|
+
let search = configRoot;
|
|
3349
|
+
for (let i = 0; i < 10; i++) {
|
|
3350
|
+
if (findConfigFile(search)) {
|
|
3351
|
+
configRoot = search;
|
|
3352
|
+
break;
|
|
3353
|
+
}
|
|
3354
|
+
const parent = resolve2(search, "..");
|
|
3355
|
+
if (parent === search) break;
|
|
3356
|
+
search = parent;
|
|
3357
|
+
}
|
|
3358
|
+
return configRoot;
|
|
3359
|
+
}
|
|
3360
|
+
function createWorkspace(files) {
|
|
3361
|
+
const configRoot = findConfigRoot(files);
|
|
3362
|
+
const projectConfig = loadConfigFromDisk(configRoot);
|
|
3363
|
+
const workspace = new WorkspaceModel();
|
|
3364
|
+
const fileContents = /* @__PURE__ */ new Map();
|
|
3365
|
+
for (const filePath of files) {
|
|
3366
|
+
try {
|
|
3367
|
+
const text = readFileSync6(filePath, "utf-8");
|
|
3368
|
+
const uri = pathToFileURL3(filePath).toString();
|
|
3369
|
+
fileContents.set(uri, text);
|
|
3370
|
+
} catch {
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
workspace.initialize(fileContents);
|
|
3374
|
+
if (Object.keys(projectConfig.macros).length > 0) {
|
|
3375
|
+
workspace.macros.loadSupplements(projectConfig.macros);
|
|
3376
|
+
}
|
|
3377
|
+
return workspace;
|
|
3378
|
+
}
|
|
3379
|
+
async function startMcpServer() {
|
|
3380
|
+
const server = new McpServer({
|
|
3381
|
+
name: "spindle-lsp",
|
|
3382
|
+
version: "0.3.5"
|
|
3383
|
+
});
|
|
3384
|
+
server.tool(
|
|
3385
|
+
"spindle_check",
|
|
3386
|
+
"Run diagnostics on .tw/.twee files. Returns structured diagnostic results.",
|
|
3387
|
+
{
|
|
3388
|
+
path: z.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to check"),
|
|
3389
|
+
severity: z.enum(["error", "warning", "info", "hint"]).optional().describe("Minimum severity to include")
|
|
3390
|
+
},
|
|
3391
|
+
async (args2) => {
|
|
3392
|
+
const cwd = process.cwd();
|
|
3393
|
+
const files = await resolveFiles(args2.path, cwd);
|
|
3394
|
+
if (files.length === 0) {
|
|
3395
|
+
return {
|
|
3396
|
+
content: [{ type: "text", text: JSON.stringify([], null, 2) }]
|
|
3397
|
+
};
|
|
3398
|
+
}
|
|
3399
|
+
const workspace = createWorkspace(files);
|
|
3400
|
+
try {
|
|
3401
|
+
const results = [];
|
|
3402
|
+
for (const [uri] of workspace.documents.getUris().map((u) => [u])) {
|
|
3403
|
+
let diags = computeDiagnostics(uri, workspace);
|
|
3404
|
+
if (args2.severity) {
|
|
3405
|
+
diags = filterBySeverity2(diags, args2.severity);
|
|
3406
|
+
}
|
|
3407
|
+
const filePath = fileURLToPath3(uri);
|
|
3408
|
+
const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
3409
|
+
for (const d of diags) {
|
|
3410
|
+
results.push({
|
|
3411
|
+
file: relativePath,
|
|
3412
|
+
line: d.range.start.line + 1,
|
|
3413
|
+
column: d.range.start.character + 1,
|
|
3414
|
+
severity: d.severity,
|
|
3415
|
+
code: d.code,
|
|
3416
|
+
message: d.message
|
|
3417
|
+
});
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
return {
|
|
3421
|
+
content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
|
|
3422
|
+
};
|
|
3423
|
+
} finally {
|
|
3424
|
+
workspace.dispose();
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
);
|
|
3428
|
+
server.tool(
|
|
3429
|
+
"spindle_format",
|
|
3430
|
+
"Format .tw/.twee files in place.",
|
|
3431
|
+
{
|
|
3432
|
+
path: z.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to format")
|
|
3433
|
+
},
|
|
3434
|
+
async (args2) => {
|
|
3435
|
+
const cwd = process.cwd();
|
|
3436
|
+
const files = await resolveFiles(args2.path, cwd);
|
|
3437
|
+
let formatted = 0;
|
|
3438
|
+
let unchanged = 0;
|
|
3439
|
+
const changedFiles = [];
|
|
3440
|
+
for (const filePath of files) {
|
|
3441
|
+
try {
|
|
3442
|
+
const text = readFileSync6(filePath, "utf-8");
|
|
3443
|
+
const result = await formatDocument(text);
|
|
3444
|
+
if (result !== text) {
|
|
3445
|
+
writeFileSync2(filePath, result, "utf-8");
|
|
3446
|
+
formatted++;
|
|
3447
|
+
const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
3448
|
+
changedFiles.push(relativePath);
|
|
3449
|
+
} else {
|
|
3450
|
+
unchanged++;
|
|
3451
|
+
}
|
|
3452
|
+
} catch {
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
const output = { formatted, unchanged, files: changedFiles };
|
|
3456
|
+
return {
|
|
3457
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
);
|
|
3461
|
+
server.tool(
|
|
3462
|
+
"spindle_format_check",
|
|
3463
|
+
"Check formatting without modifying files. Returns list of files that need formatting.",
|
|
3464
|
+
{
|
|
3465
|
+
path: z.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to check")
|
|
3466
|
+
},
|
|
3467
|
+
async (args2) => {
|
|
3468
|
+
const cwd = process.cwd();
|
|
3469
|
+
const files = await resolveFiles(args2.path, cwd);
|
|
3470
|
+
const needsFormatting = [];
|
|
3471
|
+
const alreadyFormatted = [];
|
|
3472
|
+
for (const filePath of files) {
|
|
3473
|
+
try {
|
|
3474
|
+
const text = readFileSync6(filePath, "utf-8");
|
|
3475
|
+
const result = await formatDocument(text);
|
|
3476
|
+
const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
3477
|
+
if (result !== text) {
|
|
3478
|
+
needsFormatting.push(relativePath);
|
|
3479
|
+
} else {
|
|
3480
|
+
alreadyFormatted.push(relativePath);
|
|
3481
|
+
}
|
|
3482
|
+
} catch {
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
const output = { needsFormatting, alreadyFormatted };
|
|
3486
|
+
return {
|
|
3487
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
3490
|
+
);
|
|
3491
|
+
const transport = new StdioServerTransport();
|
|
3492
|
+
await server.connect(transport);
|
|
3493
|
+
}
|
|
3494
|
+
var SEVERITY_ORDER2;
|
|
3495
|
+
var init_server = __esm({
|
|
3496
|
+
"src/mcp/server.ts"() {
|
|
3497
|
+
"use strict";
|
|
3498
|
+
init_workspace_model();
|
|
3499
|
+
init_config_loader();
|
|
3500
|
+
init_diagnostics();
|
|
3501
|
+
init_format();
|
|
3502
|
+
SEVERITY_ORDER2 = ["hint", "info", "warning", "error"];
|
|
3503
|
+
}
|
|
3504
|
+
});
|
|
3505
|
+
|
|
2982
3506
|
// src/core/plugin/plugin-loader.ts
|
|
2983
3507
|
function loadPlugins(allPlugins2, config) {
|
|
2984
3508
|
const disabled = new Set(config.disabledPlugins ?? []);
|
|
@@ -4833,8 +5357,8 @@ var init_plugins = __esm({
|
|
|
4833
5357
|
});
|
|
4834
5358
|
|
|
4835
5359
|
// src/server/server.ts
|
|
4836
|
-
var
|
|
4837
|
-
__export(
|
|
5360
|
+
var server_exports2 = {};
|
|
5361
|
+
__export(server_exports2, {
|
|
4838
5362
|
startServer: () => startServer
|
|
4839
5363
|
});
|
|
4840
5364
|
import {
|
|
@@ -4844,18 +5368,18 @@ import {
|
|
|
4844
5368
|
FileChangeType
|
|
4845
5369
|
} from "vscode-languageserver/node.js";
|
|
4846
5370
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
4847
|
-
import { fileURLToPath as
|
|
4848
|
-
import { readFileSync as
|
|
4849
|
-
import { glob as
|
|
5371
|
+
import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
5372
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
5373
|
+
import { glob as glob4 } from "glob";
|
|
4850
5374
|
function uriToFsPath(uri) {
|
|
4851
5375
|
try {
|
|
4852
|
-
return
|
|
5376
|
+
return fileURLToPath4(uri);
|
|
4853
5377
|
} catch {
|
|
4854
5378
|
return uri;
|
|
4855
5379
|
}
|
|
4856
5380
|
}
|
|
4857
5381
|
function fsPathToUri(fsPath) {
|
|
4858
|
-
return
|
|
5382
|
+
return pathToFileURL4(fsPath).toString();
|
|
4859
5383
|
}
|
|
4860
5384
|
function startServer(_args) {
|
|
4861
5385
|
const connection = createConnection(ProposedFeatures.all);
|
|
@@ -4954,7 +5478,7 @@ function startServer(_args) {
|
|
|
4954
5478
|
} else {
|
|
4955
5479
|
try {
|
|
4956
5480
|
const fsPath = uriToFsPath(change.uri);
|
|
4957
|
-
const text =
|
|
5481
|
+
const text = readFileSync7(fsPath, "utf-8");
|
|
4958
5482
|
if (workspace.documents.has(change.uri)) {
|
|
4959
5483
|
workspace.documents.update(change.uri, text);
|
|
4960
5484
|
} else {
|
|
@@ -4977,14 +5501,14 @@ async function scanWorkspaceFiles(root) {
|
|
|
4977
5501
|
const contents = /* @__PURE__ */ new Map();
|
|
4978
5502
|
if (!root) return contents;
|
|
4979
5503
|
try {
|
|
4980
|
-
const files = await
|
|
5504
|
+
const files = await glob4("**/*.{tw,twee}", {
|
|
4981
5505
|
cwd: root,
|
|
4982
5506
|
absolute: true,
|
|
4983
5507
|
nodir: true
|
|
4984
5508
|
});
|
|
4985
5509
|
for (const filePath of files) {
|
|
4986
5510
|
try {
|
|
4987
|
-
const text =
|
|
5511
|
+
const text = readFileSync7(filePath, "utf-8");
|
|
4988
5512
|
const uri = fsPathToUri(filePath);
|
|
4989
5513
|
contents.set(uri, text);
|
|
4990
5514
|
} catch {
|
|
@@ -4994,7 +5518,7 @@ async function scanWorkspaceFiles(root) {
|
|
|
4994
5518
|
}
|
|
4995
5519
|
return contents;
|
|
4996
5520
|
}
|
|
4997
|
-
var
|
|
5521
|
+
var init_server2 = __esm({
|
|
4998
5522
|
"src/server/server.ts"() {
|
|
4999
5523
|
"use strict";
|
|
5000
5524
|
init_workspace_model();
|
|
@@ -5015,8 +5539,11 @@ if (args[0] === "check") {
|
|
|
5015
5539
|
const { runFormat: runFormat2 } = await Promise.resolve().then(() => (init_format2(), format_exports));
|
|
5016
5540
|
const exitCode = await runFormat2(args.slice(1));
|
|
5017
5541
|
process.exit(exitCode);
|
|
5542
|
+
} else if (args[0] === "mcp") {
|
|
5543
|
+
const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
5544
|
+
await startMcpServer2();
|
|
5018
5545
|
} else {
|
|
5019
|
-
const { startServer: startServer2 } = await Promise.resolve().then(() => (
|
|
5546
|
+
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server2(), server_exports2));
|
|
5020
5547
|
startServer2(args);
|
|
5021
5548
|
}
|
|
5022
5549
|
//# sourceMappingURL=bin.js.map
|