Skip to content
Closed
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
89 changes: 82 additions & 7 deletions rhwp-studio/src/engine/input-handler-keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,18 +680,26 @@ export function onKeyDown(this: any, e: KeyboardEvent): void {
}

// Alt 조합 단축키 처리
if (e.altKey && this.dispatcher) {
if (e.altKey) {
// Alt+Backspace → 단어 단위 삭제 (macOS Option+Delete)
if (e.key === 'Backspace' && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
deleteWordBackward.call(this);
return;
}
// Alt+V → Chord 대기 (보기 메뉴 단축키, 한컴 Alt+V,T 계승)
if ((e.key === 'v' || e.key === 'V' || e.key === 'ㅍ') && !e.shiftKey && !e.ctrlKey) {
e.preventDefault();
this._pendingChordV = true;
return;
}
const cmdId = matchShortcut(e, defaultShortcuts);
if (cmdId) {
e.preventDefault();
this.dispatcher.dispatch(cmdId);
return;
if (this.dispatcher) {
const cmdId = matchShortcut(e, defaultShortcuts);
if (cmdId) {
e.preventDefault();
this.dispatcher.dispatch(cmdId);
return;
}
}
}

Expand Down Expand Up @@ -892,8 +900,17 @@ export function handleCtrlKey(this: any, e: KeyboardEvent): void {
return;
}

// 커맨드 시스템에 없는 직접 처리 (Ctrl+Home/End 등 커서 이동)
// 커맨드 시스템에 없는 직접 처리 (Ctrl+Home/End, Ctrl+Backspace 등)
switch (e.key.toLowerCase()) {
case 'backspace': {
e.preventDefault();
if (e.metaKey) {
deleteToLineStart.call(this);
} else {
deleteWordBackward.call(this);
}
break;
}
case 'home': {
e.preventDefault();
if (e.shiftKey) {
Expand Down Expand Up @@ -930,6 +947,64 @@ export function handleSelectAll(this: any): void {
this.updateCaret();
}

/** Cmd+Backspace (macOS): 현재 위치에서 줄 시작까지 삭제 */
function deleteToLineStart(this: any): void {
if (this.cursor.hasSelection()) {
this.deleteSelection();
return;
}
this.cursor.setAnchor();
this.cursor.moveToLineStart();
if (this.cursor.hasSelection()) {
this.deleteSelection();
}
}

/** Ctrl+Backspace (Win/Linux) / Alt+Backspace (macOS): 이전 단어 경계까지 삭제 */
function deleteWordBackward(this: any): void {
if (this.cursor.hasSelection()) {
this.deleteSelection();
return;
}
const pos = this.cursor.getPosition();
const inCell = this.cursor.isInCell();
const charOffset = inCell ? (pos.cellCharOffset ?? pos.charOffset) : pos.charOffset;
if (charOffset === 0) {
this.handleBackspace(pos, inCell);
return;
}
const text = getTextBeforeCursor(this.wasm, pos, inCell, charOffset);
const wordStart = findWordBoundaryInText(text);
if (wordStart < charOffset) {
this.cursor.setAnchor();
const target = inCell
? { ...pos, cellCharOffset: wordStart, charOffset: wordStart }
: { ...pos, charOffset: wordStart };
this.cursor.moveTo(target);
this.deleteSelection();
}
}

function getTextBeforeCursor(wasm: WasmBridge, pos: any, inCell: boolean, offset: number): string {
if (inCell && pos.parentParaIndex != null && pos.controlIndex != null && pos.cellIndex != null && pos.cellParaIndex != null) {
try {
return wasm.getTextInCell(pos.sectionIndex, pos.parentParaIndex, pos.controlIndex, pos.cellIndex, pos.cellParaIndex, 0, offset);
} catch { /* fallback */ }
}
return wasm.getTextRange(pos.sectionIndex, pos.paragraphIndex, 0, offset);
}

function findWordBoundaryInText(text: string): number {
let i = text.length - 1;
Comment on lines +969 to +998
while (i >= 0 && isWordSeparator(text[i])) i--;
while (i >= 0 && !isWordSeparator(text[i])) i--;
return i + 1;
}

function isWordSeparator(ch: string): boolean {
return /[\s .,;:!?'"()[\]{}<>\/\\|@#$%^&*~`+=\-_]/.test(ch);
}

export function onCopy(this: any, e: ClipboardEvent): void {
if (!this.active) return;

Expand Down