@pierre/diffs
Version:
363 lines (362 loc) • 10.8 kB
JavaScript
//#region src/editor/languages.ts
const DEFAULT_LINE_COMMENT = "//";
const DEFAULT_BLOCK_COMMENT = ["/*", "*/"];
const LANGUAGE_COMMENT_CONFIGS = {
sql: { lineComment: "--" },
ruby: {
lineComment: "#",
blockComment: ["=begin", "=end"]
},
rst: { lineComment: ".." },
coffeescript: {
lineComment: "#",
blockComment: ["###", "###"]
},
cmd: { lineComment: "@REM" },
julia: {
lineComment: "#",
blockComment: ["#=", "=#"]
},
yaml: { lineComment: "#" },
yml: { lineComment: "#" },
markdown: {
lineComment: null,
blockComment: ["<!--", "-->"]
},
zsh: { lineComment: "#" },
makefile: { lineComment: "#" },
handlebars: {
lineComment: null,
blockComment: ["{{!--", "--}}"]
},
ini: {
lineComment: ";",
blockComment: [";", " "]
},
powershell: {
lineComment: "#",
blockComment: ["<#", "#>"]
},
vb: { lineComment: "'" },
xml: {
lineComment: null,
blockComment: ["<!--", "-->"]
},
lua: {
lineComment: "--",
blockComment: ["--[[", "]]"]
},
html: {
lineComment: null,
blockComment: ["<!--", "-->"]
},
diff: {
lineComment: "#",
blockComment: ["#", " "]
},
r: { lineComment: "#" },
fsharp: { blockComment: ["(*", "*)"] },
pug: { lineComment: "//-" },
perl: { lineComment: "#" },
tex: { lineComment: "%" },
clojure: { lineComment: ";;" },
css: { lineComment: null },
python: {
lineComment: "#",
blockComment: ["\"\"\"", "\"\"\""]
},
dotenv: { lineComment: "#" },
dockerfile: { lineComment: "#" },
razor: {
lineComment: null,
blockComment: ["<!--", "-->"]
},
prompt: {
lineComment: null,
blockComment: ["<!--", "-->"]
}
};
/** Resolves language-specific comment tokens over the editor defaults. */
function resolveCommentConfig(languageId, overrides) {
const config = {
...LANGUAGE_COMMENT_CONFIGS[languageId],
...overrides?.[languageId]
};
return {
lineComment: config.lineComment === void 0 ? DEFAULT_LINE_COMMENT : config.lineComment,
blockComment: config.blockComment ?? DEFAULT_BLOCK_COMMENT
};
}
/** Builds one aligned batch of line-comment edits for all selected lines. */
function resolveLineCommentEdits(textDocument, selections, token) {
const lines = [];
const seenLines = /* @__PURE__ */ new Set();
for (const selection of selections) {
let endLine = selection.end.line;
if (selection.start.line < endLine && selection.end.character === 0) endLine--;
const startIndex = lines.length;
let minIndent = Infinity;
for (let line = selection.start.line; line <= endLine; line++) {
if (seenLines.has(line)) continue;
seenLines.add(line);
const text = textDocument.getLineText(line);
const indent = text.length - text.trimStart().length;
const empty = indent === text.length;
if (!empty) minIndent = Math.min(minIndent, indent);
lines.push({
line,
comment: text.slice(indent, indent + token.length) === token ? indent : -1,
empty,
indent,
single: false
});
}
if (minIndent !== Infinity) {
for (let index = startIndex; index < lines.length; index++) if (!lines[index].empty) lines[index].indent = minIndent;
}
if (lines.length === startIndex + 1) lines[startIndex].single = true;
}
const shouldComment = lines.some((line) => line.comment < 0 && (!line.empty || line.single));
const edits = [];
if (shouldComment) {
for (const line of lines) {
if (line.empty && !line.single) continue;
const position = {
line: line.line,
character: line.indent
};
edits.push({
range: {
start: position,
end: position
},
newText: token + " "
});
}
return edits;
}
for (const line of lines) {
if (line.comment < 0) continue;
const text = textDocument.getLineText(line.line);
const start = line.comment;
const end = start + token.length + (text[start + token.length] === " " ? 1 : 0);
edits.push({
range: {
start: {
line: line.line,
character: start
},
end: {
line: line.line,
character: end
}
},
newText: ""
});
}
return edits;
}
const BLOCK_COMMENT_SEARCH_MARGIN = 50;
function findBlockComment(textDocument, open, close, from, to) {
const beforeStart = Math.max(0, from - BLOCK_COMMENT_SEARCH_MARGIN);
const textBefore = textDocument.getTextSlice(beforeStart, from);
const textAfter = textDocument.getTextSlice(to, to + BLOCK_COMMENT_SEARCH_MARGIN);
const spaceBefore = textBefore.length - textBefore.trimEnd().length;
const spaceAfter = textAfter.length - textAfter.trimStart().length;
const beforeOffset = textBefore.length - spaceBefore;
if (textBefore.slice(beforeOffset - open.length, beforeOffset) === open && textAfter.slice(spaceAfter, spaceAfter + close.length) === close) return {
open: {
start: from - spaceBefore - open.length,
end: from - spaceBefore + (spaceBefore > 0 ? 1 : 0),
text: ""
},
close: {
start: to + spaceAfter - (spaceAfter > 0 ? 1 : 0),
end: to + spaceAfter + close.length,
text: ""
},
contentStart: from,
contentEnd: to
};
const shortText = to - from <= BLOCK_COMMENT_SEARCH_MARGIN * 2 ? textDocument.getTextSlice(from, to) : void 0;
const startText = shortText ?? textDocument.getTextSlice(from, from + BLOCK_COMMENT_SEARCH_MARGIN);
const endText = shortText ?? textDocument.getTextSlice(to - BLOCK_COMMENT_SEARCH_MARGIN, to);
const startSpace = startText.length - startText.trimStart().length;
const closeStart = to - (endText.length - endText.trimEnd().length) - close.length;
if (startText.slice(startSpace, startSpace + open.length) !== open || textDocument.getTextSlice(closeStart, closeStart + close.length) !== close) return;
const openStart = from + startSpace;
const openEnd = openStart + open.length + (/\s/.test(startText[open.length + startSpace] ?? "") ? 1 : 0);
const closeDeleteStart = closeStart - (/\s/.test(textDocument.getTextSlice(closeStart - 1, closeStart)) ? 1 : 0);
return {
open: {
start: openStart,
end: openEnd,
text: ""
},
close: {
start: closeDeleteStart,
end: closeStart + close.length,
text: ""
},
contentStart: openEnd,
contentEnd: closeDeleteStart
};
}
function mapOffset(offset, edits, cumulativeDeltas, association) {
let low = 0;
let high = edits.length;
while (low < high) {
const middle = low + high >>> 1;
if (edits[middle].end < offset) low = middle + 1;
else high = middle;
}
let delta = cumulativeDeltas[low];
for (let index = low; index < edits.length; index++) {
const edit = edits[index];
if (offset < edit.start) break;
if (edit.start === edit.end && offset === edit.start) {
if (association < 0) break;
delta += edit.text.length;
continue;
}
if (offset > edit.end) {
delta += edit.text.length - (edit.end - edit.start);
continue;
}
if (offset === edit.end) return edit.start + delta + edit.text.length;
return edit.start + delta + (association > 0 ? edit.text.length : 0);
}
return offset + delta;
}
/** Builds block-comment edits and content-preserving post-edit selections. */
function resolveBlockCommentEdits(textDocument, selections, [open, close], linewise = false) {
const ranges = selections.map((selection) => {
let { start, end } = selection;
if (linewise) {
let endLine = end.line;
if (start.line < endLine && end.character === 0) endLine--;
const firstLine = textDocument.getLineText(start.line);
const indent = firstLine.length - firstLine.trimStart().length;
start = {
line: start.line,
character: indent
};
end = {
line: endLine,
character: textDocument.getLineLength(endLine)
};
}
return {
from: textDocument.offsetAt(start),
to: textDocument.offsetAt(end),
direction: selection.direction,
comment: void 0
};
});
if (linewise && ranges.length > 1) {
ranges.sort((a, b) => {
const startOrder = a.from - b.from;
return startOrder !== 0 ? startOrder : a.to - b.to;
});
let lastRangeIndex = 0;
for (let index = 1; index < ranges.length; index++) {
const previous = ranges[lastRangeIndex];
const current = ranges[index];
if (current.from <= previous.to) previous.to = Math.max(previous.to, current.to);
else ranges[++lastRangeIndex] = current;
}
ranges.length = lastRangeIndex + 1;
}
for (const range of ranges) range.comment = findBlockComment(textDocument, open, close, range.from, range.to);
const shouldUncomment = ranges.every((range) => range.comment !== void 0);
const offsetEdits = [];
for (const range of ranges) {
const comment = range.comment;
if (shouldUncomment && comment !== void 0) {
offsetEdits.push(comment.open, comment.close);
continue;
}
if (comment !== void 0) continue;
if (range.from === range.to) {
offsetEdits.push({
start: range.from,
end: range.to,
text: open + " " + close
});
continue;
}
offsetEdits.push({
start: range.from,
end: range.from,
text: open + " "
}, {
start: range.to,
end: range.to,
text: " " + close
});
}
if (offsetEdits.length === 0) return;
offsetEdits.sort((a, b) => {
const startOrder = a.start - b.start;
return startOrder !== 0 ? startOrder : a.end - b.end;
});
const edits = offsetEdits.map((edit) => ({
range: {
start: textDocument.positionAt(edit.start),
end: textDocument.positionAt(edit.end)
},
newText: edit.text
}));
if (linewise) return {
edits,
nextSelectionOffsets: []
};
const logicalRanges = ranges.map((range) => {
const comment = range.comment;
if (shouldUncomment && comment !== void 0) return {
start: comment.contentStart,
end: comment.contentEnd,
startAssociation: 1,
endAssociation: -1
};
if (comment !== void 0) return {
start: range.from,
end: range.to,
startAssociation: 1,
endAssociation: 1
};
if (range.from === range.to) return {
start: range.from,
end: range.to,
startAssociation: -1,
endAssociation: -1,
collapsedInset: open.length + 1
};
return {
start: range.from,
end: range.to,
startAssociation: 1,
endAssociation: -1
};
});
const cumulativeDeltas = new Array(offsetEdits.length + 1);
cumulativeDeltas[0] = 0;
for (let index = 0; index < offsetEdits.length; index++) {
const edit = offsetEdits[index];
cumulativeDeltas[index + 1] = cumulativeDeltas[index] + edit.text.length - (edit.end - edit.start);
}
return {
edits,
nextSelectionOffsets: logicalRanges.map((range, index) => {
const start = mapOffset(range.start, offsetEdits, cumulativeDeltas, range.startAssociation) + (range.collapsedInset ?? 0);
return [
start,
range.collapsedInset === void 0 ? mapOffset(range.end, offsetEdits, cumulativeDeltas, range.endAssociation) : start,
ranges[index].direction
];
})
};
}
//#endregion
export { resolveBlockCommentEdits, resolveCommentConfig, resolveLineCommentEdits };
//# sourceMappingURL=languages.js.map