feat: terse transformations of image paths in text buffer (#4924)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
Co-authored-by: owenofbrien <86964623+owenofbrien@users.noreply.github.com>
This commit is contained in:
Pyush Sinha
2025-12-23 12:46:09 -08:00
committed by GitHub
parent 02a36afc38
commit 873d10df42
9 changed files with 772 additions and 75 deletions

View File

@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type { Transformation } from '../components/shared/text-buffer.js';
import { cpLen, cpSlice } from './textUtils.js';
export type HighlightToken = {
@@ -20,57 +21,81 @@ const HIGHLIGHT_REGEX = /(^\/[a-zA-Z0-9_-]+|@(?:\\ |[^,\s;!?()[\]{}])+)/g;
export function parseInputForHighlighting(
text: string,
index: number,
transformations: Transformation[] = [],
cursorCol?: number,
): readonly HighlightToken[] {
HIGHLIGHT_REGEX.lastIndex = 0;
if (!text) {
return [{ text: '', type: 'default' }];
}
const parseUntransformedInput = (text: string): HighlightToken[] => {
const tokens: HighlightToken[] = [];
if (!text) return tokens;
HIGHLIGHT_REGEX.lastIndex = 0;
let last = 0;
let match: RegExpExecArray | null;
while ((match = HIGHLIGHT_REGEX.exec(text)) !== null) {
const [fullMatch] = match;
const matchIndex = match.index;
if (matchIndex > last) {
tokens.push({ text: text.slice(last, matchIndex), type: 'default' });
}
const type = fullMatch.startsWith('/') ? 'command' : 'file';
if (type === 'command' && index !== 0) {
tokens.push({ text: fullMatch, type: 'default' });
} else {
tokens.push({ text: fullMatch, type });
}
last = matchIndex + fullMatch.length;
}
if (last < text.length) {
tokens.push({ text: text.slice(last), type: 'default' });
}
return tokens;
};
const tokens: HighlightToken[] = [];
let lastIndex = 0;
let match;
while ((match = HIGHLIGHT_REGEX.exec(text)) !== null) {
const [fullMatch] = match;
const matchIndex = match.index;
let column = 0;
const sortedTransformations = (transformations ?? [])
.slice()
.sort((a, b) => a.logStart - b.logStart);
// Add the text before the match as a default token
if (matchIndex > lastIndex) {
tokens.push({
text: text.slice(lastIndex, matchIndex),
type: 'default',
});
}
for (const transformation of sortedTransformations) {
const textBeforeTransformation = cpSlice(
text,
column,
transformation.logStart,
);
tokens.push(...parseUntransformedInput(textBeforeTransformation));
// Add the matched token
const type = fullMatch.startsWith('/') ? 'command' : 'file';
// Only highlight slash commands if the index is 0.
if (type === 'command' && index !== 0) {
tokens.push({
text: fullMatch,
type: 'default',
});
} else {
tokens.push({
text: fullMatch,
type,
});
}
const isCursorInside =
typeof cursorCol === 'number' &&
cursorCol >= transformation.logStart &&
cursorCol <= transformation.logEnd;
const transformationText = isCursorInside
? transformation.logicalText
: transformation.collapsedText;
tokens.push({ text: transformationText, type: 'file' });
lastIndex = matchIndex + fullMatch.length;
}
// Add any remaining text after the last match
if (lastIndex < text.length) {
tokens.push({
text: text.slice(lastIndex),
type: 'default',
});
column = transformation.logEnd;
}
const textAfterFinalTransformation = cpSlice(text, column);
tokens.push(...parseUntransformedInput(textAfterFinalTransformation));
return tokens;
}
export function buildSegmentsForVisualSlice(
export function parseSegmentsFromTokens(
tokens: readonly HighlightToken[],
sliceStart: number,
sliceEnd: number,
@@ -102,6 +127,5 @@ export function buildSegmentsForVisualSlice(
tokenCpStart += tokenLen;
}
return segments;
}