Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ They are listed as follows:
| `modalEditor.resetState` | - | Reset internal state |
| `modalEditor.importKeybindings` | - | Import keybindings |
| `modalEditor.importPreset` | `string?` | Import keybindings from preset dir or a specified dir |
| `modalEditor.selectLine` | - | Select current line (allows expanding with repeated use; properly handles empty lines) |
| `modalEditor.selectToEndOfLine` | - | Select to end of line (first press: to last char, second press: to actual line end) |


Types defined in the above table:
Expand Down
27 changes: 9 additions & 18 deletions presets/helix.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,7 @@ module.exports = {
",": "removeSecondaryCursors",

J: repeatable("editor.action.joinLines"),
x: repeatable([
{
command: "cursorRightSelect",
// move right when this line is alreay selected (because of inclusive range)
when: "_ctx.selection.contains(_ctx.lineAt(_ctx.pos.line).range)"
},
"expandLineSelection",
// move left because it expands to a new line (because of inclusive range)
"cursorLeftSelect",
]),
x: repeatable("modalEditor.selectLine"),
"<": repeatable("editor.action.outdentLines"),
">": repeatable("editor.action.indentLines"),
y: [
Expand Down Expand Up @@ -94,10 +85,10 @@ module.exports = {
}`
}
},

// into command mode
":": "modalEditor.setCommandMode",

// replay last change
".": {
command: "modalEditor.replayRecord",
Expand Down Expand Up @@ -139,7 +130,7 @@ module.exports = {
// only when there is a prefix count
when: "_ctx.count !== undefined"
},

// match mode
m: {
m: "editor.action.jumpToBracket"
Expand All @@ -159,7 +150,7 @@ module.exports = {
n: "workbench.action.nextEditor",
d: "editor.action.revealDefinition"
},

// space mode
" ": {
// yank to clipboard
Expand Down Expand Up @@ -201,7 +192,7 @@ module.exports = {
k: "editor.action.showHover",
"?": "workbench.action.showCommands"
},

// search
"/": "actions.find",
n: [
Expand Down Expand Up @@ -258,7 +249,7 @@ module.exports = {
"cursorLeftSelect"
])),
b: repeatable(reselect("cursorWordStartLeftSelect")),

// Motions
f: {
"": recordMotion(repeatable(reselect({
Expand Down Expand Up @@ -317,7 +308,7 @@ module.exports = {
}
],
},

// set to select mode
v: "modalEditor.setSelectMode"
},
Expand Down Expand Up @@ -396,7 +387,7 @@ module.exports = {
}
]
},

// set back to normal mode
v: "modalEditor.setNormalMode"
},
Expand Down
103 changes: 103 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,107 @@ async function replayRecord(reg: string) {
await appState.replayRecord(reg);
}

/**
* Select line(s) - properly handles empty lines
* Selects entire lines and allows extending selections
*/
async function selectLine() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const newSelections = editor.selections.map((sel) => {
// Get the range of lines we're working with
let startLine = sel.isEmpty
? sel.active.line
: Math.min(sel.start.line, sel.end.line);
let endLine = sel.isEmpty
? sel.active.line
: Math.max(sel.start.line, sel.end.line);

// Check if we need to expand the selection
if (!sel.isEmpty) {
// We have a selection - check if the last line is fully included
const lastLineRange = editor.document.lineAt(endLine).range;
const lastLineText = editor.document.lineAt(endLine).text;

// Check if we're at the end of the last line (considering inclusive range)
const atEndOfLine =
lastLineText.length === 0
? sel.end.isEqual(lastLineRange.end)
: sel.end.character >= lastLineText.length - 1;

// If we're at the end of the current selection, expand to next line
if (atEndOfLine && endLine < editor.document.lineCount - 1) {
endLine++;
}
}

// Build the selection from start of first line to end of last line
const startPos = editor.document.lineAt(startLine).range.start;
const endLineText = editor.document.lineAt(endLine).text;

let endPos: vscode.Position;
if (endLineText.length === 0) {
// Empty line - select to actual end
// endPos = editor.document.lineAt(endLine).range.end;
endPos = new vscode.Position(endLine + 1, 0);
} else {
// Non-empty line - select beyond last character
endPos = new vscode.Position(endLine, endLineText.length + 1);
}

return new vscode.Selection(startPos, endPos);
});

editor.selections = newSelections;
}

/**
* Select to end of line
* First $: selects to last character
* Second $: selects to actual end of line
*/
async function selectToEndOfLine() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;

const newSelections: vscode.Selection[] = [];

for (const sel of editor.selections) {
const currentLine = sel.active.line;
const lineText = editor.document.lineAt(currentLine).text;
const lineRange = editor.document.lineAt(currentLine).range;

let anchor: vscode.Position;
let active: vscode.Position;

if (lineText.length === 0) {
// Empty line - select the entire line
anchor = lineRange.start;
active = lineRange.end;
} else {
// Non-empty line
// Use current position as anchor if no selection, otherwise keep existing anchor
anchor = sel.isEmpty ? sel.active : sel.anchor;

// Check if we're already at the last character position
const atLastChar = sel.active.character === lineText.length - 1;

if (!sel.isEmpty && atLastChar) {
// Second $ - we're already at last char, now go to actual end
active = lineRange.end;
} else {
// First $ - go to last character (inclusive range)
active = new vscode.Position(currentLine, lineText.length - 1);
}
}

newSelections.push(new vscode.Selection(anchor, active));
}

editor.selections = newSelections;
}

// Execute a command with the current context
export async function executeCommand(command: Command) {
if (!isCommand(command)) {
Expand Down Expand Up @@ -690,6 +791,8 @@ export async function register(context: vscode.ExtensionContext, outputChannel:
registerCommand(resetState),
registerCommand(importKeybindings),
registerCommand(importPreset),
registerCommand(selectLine),
registerCommand(selectToEndOfLine),
// Handle type events
vscode.commands.registerCommand("type", onType)
);
Expand Down