Skip to content

Commit d6eeff4

Browse files
committed
feat: add g_ motion (move to last non-blank character)
Implements the g_ motion from vim, which moves to the last non-whitespace character on the line. Supports counts (e.g. 2g_ moves to the last non-blank of the next line) and works correctly with operators (dg_ deletes to last non-blank, preserving trailing whitespace).
1 parent c912733 commit d6eeff4

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

src/vim.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export function initVim(CM) {
138138
{ keys: "g$", type: "motion", motion: "moveToEndOfDisplayLine" },
139139
{ keys: "g^", type: "motion", motion: "moveToStartOfDisplayLine" },
140140
{ keys: "g0", type: "motion", motion: "moveToStartOfDisplayLine" },
141+
{ keys: "g_", type: "motion", motion: "moveToLastNonWhiteSpaceCharacter", motionArgs: { inclusive: true }},
141142
{ keys: '0', type: 'motion', motion: 'moveToStartOfLine' },
142143
{ keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' },
143144
{ keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }},
@@ -2537,6 +2538,12 @@ export function initVim(CM) {
25372538
return new Pos(cursor.line,
25382539
findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)));
25392540
},
2541+
moveToLastNonWhiteSpaceCharacter: function(cm, head, motionArgs) {
2542+
var line = head.line + motionArgs.repeat - 1;
2543+
var lineText = cm.getLine(line);
2544+
var lastNonWS = findLastNonWhiteSpaceCharacter(lineText);
2545+
return new Pos(line, Math.max(0, lastNonWS));
2546+
},
25402547
moveToMatchedSymbol: function(cm, head) {
25412548
var cursor = head;
25422549
var line = cursor.line;
@@ -4018,6 +4025,15 @@ export function initVim(CM) {
40184025
return firstNonWS == -1 ? text.length : firstNonWS;
40194026
}
40204027

4028+
/** @arg {string} [text] */
4029+
function findLastNonWhiteSpaceCharacter(text) {
4030+
if (!text) {
4031+
return 0;
4032+
}
4033+
var index = text.search(/\s+$/);
4034+
return index == -1 ? text.length - 1 : index - 1;
4035+
}
4036+
40214037
/**
40224038
* @arg {CodeMirror} cm
40234039
* @arg {{inclusive?: boolean, innerWord?: boolean, bigWord?: boolean, noSymbol?: boolean, multiline?: boolean}} options

test/vim_test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,24 @@ testVim('g0_g$', function(cm, vim, helpers) {
541541
is(!/\.$/.test(cm.getValue()));
542542

543543
},{ lineNumbers: false, lineWrapping:true, value: 'This line is long to test movement of g$ and g0 over wrapped lines.' });
544+
testVim('g_', function(cm, vim, helpers) {
545+
// Test basic g_ on line with trailing spaces
546+
cm.setCursor(0, 0);
547+
helpers.doKeys('g', '_');
548+
helpers.assertCursorAt(0, 2);
549+
// Test g_ on line without trailing spaces
550+
cm.setCursor(1, 0);
551+
helpers.doKeys('g', '_');
552+
helpers.assertCursorAt(1, 2);
553+
// Test g_ with count (2g_ goes to last non-whitespace of next line)
554+
cm.setCursor(0, 0);
555+
helpers.doKeys('2', 'g', '_');
556+
helpers.assertCursorAt(1, 2);
557+
// Test dg_ deletes to last non-whitespace (preserving trailing spaces)
558+
cm.setCursor(0, 0);
559+
helpers.doKeys('d', 'g', '_');
560+
eq(' \nfoo', cm.getValue());
561+
}, { value: 'foo \nfoo' });
544562
testVim('}', function(cm, vim, helpers) {
545563
cm.setCursor(0, 0);
546564
helpers.doKeys('}');

0 commit comments

Comments
 (0)