Skip to content

Commit 2346e7e

Browse files
committed
Implement delete word before/after
1 parent 75928b9 commit 2346e7e

4 files changed

Lines changed: 71 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ This [Obsidian](https://obsidian.md) plugin adds keyboard shortcuts (hotkeys) co
3535
| Go to line number | Not set |
3636
| Delete to start of line | Not set |
3737
| Delete to end of line | Not set |
38+
| Delete word before cursor | Not set |
39+
| Delete word after cursor | Not set |
3840
| Transform selection to uppercase | Not set |
3941
| Transform selection to lowercase | Not set |
4042
| Transform selection to title case | Not set |

src/__tests__/actions-cursor.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
insertCursorAbove,
2222
insertCursorBelow,
2323
moveWord,
24+
deleteWord,
2425
selectWord,
2526
} from '../actions';
2627
import { CASE, CODE_EDITOR } from '../constants';
@@ -481,6 +482,39 @@ describe('Code Editor Shortcuts: actions - single cursor selection', () => {
481482
});
482483
});
483484

485+
describe('deleteWord', () => {
486+
// deleteWord computes the deletion range via moveWord (goWordLeft /
487+
// goWordRight), so forward the mocked `exec` to the real CodeMirror
488+
// command in order to verify the resulting document mutation
489+
beforeEach(() => {
490+
(editor.exec as jest.Mock).mockImplementation((name: string) =>
491+
(editor as any).execCommand(name),
492+
);
493+
});
494+
495+
afterEach(() => {
496+
(editor.exec as jest.Mock).mockReset();
497+
});
498+
499+
it('should delete the word before the cursor', () => {
500+
editor.setCursor({ line: 1, ch: 5 }); // 'dolor{cursor} sit'
501+
deleteWord(editor as any, 'before');
502+
503+
const { doc, cursor } = getDocumentAndSelection(editor);
504+
expect(doc).toEqual('lorem ipsum\n sit\namet');
505+
expect(cursor).toEqual(expect.objectContaining({ line: 1, ch: 0 }));
506+
});
507+
508+
it('should delete the word after the cursor', () => {
509+
editor.setCursor({ line: 1, ch: 0 }); // '{cursor}dolor sit'
510+
deleteWord(editor as any, 'after');
511+
512+
const { doc, cursor } = getDocumentAndSelection(editor);
513+
expect(doc).toEqual('lorem ipsum\n sit\namet');
514+
expect(cursor).toEqual(expect.objectContaining({ line: 1, ch: 0 }));
515+
});
516+
});
517+
484518
describe('selectWord', () => {
485519
it('should extend the selection to the previous word, preserving the anchor', () => {
486520
editor.setSelections([

src/actions.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,28 @@ export const moveWord = (editor: Editor, direction: 'left' | 'right') => {
511511
}
512512
};
513513

514+
// There is no built-in command for deleting a word (unlike goWordLeft /
515+
// goWordRight, delWordBefore / delWordAfter are not supported by Obsidian's
516+
// editor.exec), so the word boundary is instead found by moving the cursor
517+
// with moveWord and deleting the text passed over
518+
export const deleteWord = (editor: Editor, direction: 'before' | 'after') => {
519+
const oldPositions = editor
520+
.listSelections()
521+
.map((selection) => selection.head);
522+
moveWord(editor, direction === 'before' ? 'left' : 'right');
523+
const newPositions = editor
524+
.listSelections()
525+
.map((selection) => selection.head);
526+
527+
const changes: EditorChange[] = oldPositions.map((oldPos, index) => {
528+
const newPos = newPositions[index];
529+
const [from, to] =
530+
direction === 'before' ? [newPos, oldPos] : [oldPos, newPos];
531+
return { from, to, text: '' };
532+
});
533+
editor.transaction({ changes });
534+
};
535+
514536
// There is no built-in command for extending the selection by one word, so
515537
// the anchor of each selection is preserved across a plain word-move
516538
export const selectWord = (editor: Editor, direction: 'left' | 'right') => {

src/main.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
deleteLine,
66
deleteToStartOfLine,
77
deleteToEndOfLine,
8+
deleteWord,
89
expandSelectionToBrackets,
910
expandSelectionToQuotes,
1011
expandSelectionToQuotesOrBrackets,
@@ -102,6 +103,18 @@ export default class CodeEditorShortcuts extends Plugin {
102103
withMultipleSelections(editor, deleteToEndOfLine),
103104
});
104105

106+
this.addCommand({
107+
id: 'deleteWordBefore',
108+
name: 'Delete word before cursor',
109+
editorCallback: (editor) => deleteWord(editor, 'before'),
110+
});
111+
112+
this.addCommand({
113+
id: 'deleteWordAfter',
114+
name: 'Delete word after cursor',
115+
editorCallback: (editor) => deleteWord(editor, 'after'),
116+
});
117+
105118
this.addCommand({
106119
id: 'joinLines',
107120
name: 'Join lines',

0 commit comments

Comments
 (0)