pi-vim 0.1.9 → 0.2.1
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/README.md +62 -60
- package/index.ts +545 -129
- package/motions.ts +55 -13
- package/package.json +1 -1
- package/types.ts +1 -1
package/motions.ts
CHANGED
|
@@ -125,6 +125,27 @@ export function reverseCharMotion(motion: CharMotion): CharMotion {
|
|
|
125
125
|
return reverseMap[motion];
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
const GRAPHEME_SEGMENTER = typeof Intl !== "undefined"
|
|
129
|
+
&& typeof Intl.Segmenter === "function"
|
|
130
|
+
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
|
131
|
+
: null;
|
|
132
|
+
|
|
133
|
+
export function getLineGraphemes(line: string): Array<{ start: number; end: number }> {
|
|
134
|
+
const segments: Array<{ start: number; end: number }> = [];
|
|
135
|
+
if (GRAPHEME_SEGMENTER) {
|
|
136
|
+
for (const part of GRAPHEME_SEGMENTER.segment(line)) {
|
|
137
|
+
segments.push({ start: part.index, end: part.index + part.segment.length });
|
|
138
|
+
}
|
|
139
|
+
return segments;
|
|
140
|
+
}
|
|
141
|
+
let start = 0;
|
|
142
|
+
for (const text of Array.from(line)) {
|
|
143
|
+
segments.push({ start, end: start + text.length });
|
|
144
|
+
start += text.length;
|
|
145
|
+
}
|
|
146
|
+
return segments;
|
|
147
|
+
}
|
|
148
|
+
|
|
128
149
|
/**
|
|
129
150
|
* Find target column for a character motion (f/F/t/T).
|
|
130
151
|
* @returns target column or null if not found
|
|
@@ -141,7 +162,9 @@ export function findCharMotionTarget(
|
|
|
141
162
|
const isTill = motion === "t" || motion === "T";
|
|
142
163
|
const steps = Number.isFinite(count) && count > 0 ? Math.floor(count) : 1;
|
|
143
164
|
|
|
144
|
-
|
|
165
|
+
const graphemes = getLineGraphemes(line);
|
|
166
|
+
let currentIndex = graphemes.findIndex(g => col < g.end);
|
|
167
|
+
if (currentIndex === -1) currentIndex = graphemes.length;
|
|
145
168
|
|
|
146
169
|
for (let i = 0; i < steps; i++) {
|
|
147
170
|
const isFirst = i === 0;
|
|
@@ -149,19 +172,38 @@ export function findCharMotionTarget(
|
|
|
149
172
|
const tillRepeatOffset = isFirst && isTill && isRepeat ? 1 : 0;
|
|
150
173
|
|
|
151
174
|
if (isForward) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
let nextIndex = currentIndex + 1 + tillRepeatOffset;
|
|
176
|
+
let found = -1;
|
|
177
|
+
for (let j = nextIndex; j < graphemes.length; j++) {
|
|
178
|
+
const g = graphemes[j]!;
|
|
179
|
+
// Use startsWith to allow matching base chars if targetChar lacks combining marks,
|
|
180
|
+
// or just exact match since targetChar is typically a full grapheme.
|
|
181
|
+
if (line.slice(g.start, g.end) === targetChar || line.slice(g.start, g.end).startsWith(targetChar)) {
|
|
182
|
+
found = j;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (found === -1) return null;
|
|
187
|
+
if (isFinal) return isTill ? graphemes[found - 1]!.start : graphemes[found]!.start;
|
|
188
|
+
currentIndex = found;
|
|
189
|
+
} else {
|
|
190
|
+
let nextIndex = currentIndex - 1 - tillRepeatOffset;
|
|
191
|
+
let found = -1;
|
|
192
|
+
for (let j = nextIndex; j >= 0; j--) {
|
|
193
|
+
const g = graphemes[j]!;
|
|
194
|
+
if (line.slice(g.start, g.end) === targetChar || line.slice(g.start, g.end).startsWith(targetChar)) {
|
|
195
|
+
found = j;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (found === -1) return null;
|
|
200
|
+
if (isFinal) {
|
|
201
|
+
if (!isTill) return graphemes[found]!.start;
|
|
202
|
+
const afterTarget = graphemes[found + 1];
|
|
203
|
+
return afterTarget ? afterTarget.start : line.length;
|
|
204
|
+
}
|
|
205
|
+
currentIndex = found;
|
|
158
206
|
}
|
|
159
|
-
|
|
160
|
-
const searchStart = currentPos - 1 - tillRepeatOffset;
|
|
161
|
-
const idx = line.lastIndexOf(targetChar, searchStart);
|
|
162
|
-
if (idx === -1) return null;
|
|
163
|
-
if (isFinal) return isTill ? idx + 1 : idx;
|
|
164
|
-
currentPos = idx;
|
|
165
207
|
}
|
|
166
208
|
|
|
167
209
|
return null;
|
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -39,10 +39,10 @@ export const CHAR_MOTION_KEYS = new Set<string>(["f", "F", "t", "T"]);
|
|
|
39
39
|
// Escape sequences
|
|
40
40
|
export const ESC_LEFT = "\x1b[D";
|
|
41
41
|
export const ESC_RIGHT = "\x1b[C";
|
|
42
|
-
export const ESC_DELETE = "\x1b[3~";
|
|
43
42
|
export const CTRL_A = "\x01"; // line start
|
|
44
43
|
export const CTRL_E = "\x05"; // line end
|
|
45
44
|
export const CTRL_K = "\x0b"; // kill to end of line
|
|
45
|
+
export const CTRL_R = "\x12"; // ctrl+r — readline redo trigger in vim layer
|
|
46
46
|
export const CTRL_UNDERSCORE = "\x1f"; // ctrl+_ — readline undo
|
|
47
47
|
export const NEWLINE = "\n"; // newline character
|
|
48
48
|
export const ESC_UP = "\x1b[A"; // cursor up
|