Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1

This commit is contained in:
2026-10-16 00:05:29 +09:00
8 changed files with 447 additions and 47 deletions
@@ -41,6 +41,7 @@ import { isWaitingForSecondSelectPoint, selectingActor } from './selecting.ts';
import { isMiddleDouble, zoomExtents } from './zoom-prompt.ts';
import { attachTouch } from './input-touch.ts';
import { NUMBER_REGEXP, type PointTextEvent } from './input-patterns.ts';
import { commandCandidate, pushInput, recallInput } from './input-history.ts';
import {
angleLockHint,
getAngleLock,
@@ -74,6 +75,10 @@ const DRAG_SELECT_MIN_PX = 4;
export class InputController {
private text = '';
/** ↑↓ · Tab 돌림 — 기록인가 후보인가 · 후보의 처음 친 글 · 몇째 */
private browsing: 'history' | 'candidates' | null = null;
private candidateBase = '';
private candidateIndex = 0;
/** 왼쪽 버튼을 누른 화면 좌표 — 놓을 때 끌었는지 판단한다 */
private leftPressLocation: Point | null = null;
public readonly ime = new ImeSink((data) => {
@@ -408,7 +413,12 @@ export class InputController {
return;
}
if (evt.key === 'Tab') {
// 점 단계(기준점 있음) = 동적 입력 칸 넘김 · 그 밖 = 브라우저 초점 이동
// 쉼 + 친 글 = 명령 후보 돌림 · 점 단계(기준점 있음) = 동적 입력 칸 넘김 · 그 밖 = 브라우저 초점 이동
if (isIdle() && this.text) {
evt.preventDefault();
this.cycleCandidate(1);
return;
}
if (!dynamicInputApplies()) return;
evt.preventDefault();
const isNumber = NUMBER_REGEXP.test(this.text);
@@ -445,6 +455,7 @@ export class InputController {
// Remove the last character from the input field
evt.preventDefault();
this.text = this.text.slice(0, this.text.length - 1);
this.browsing = null;
} else if (evt.key === 'Delete') {
// User wants to delete the current selection
evt.preventDefault();
@@ -464,6 +475,9 @@ export class InputController {
this.handleEnterKey(true);
this.ime.sync(activeToolWantsIme());
}
} else if ((evt.key === 'ArrowUp' || evt.key === 'ArrowDown') && !evt.shiftKey) {
// ↑↓ = 기록 · 최근 값 · 후보(오토캐드) — 화면 옮김은 Shift+↑↓ · ← →
this.recall(evt.key === 'ArrowUp' ? -1 : 1);
} else if (evt.key === 'ArrowDown') {
// Move the screen down
getScreenCanvasDrawController().setScreenOffset(this.getScreenPanStep('down', evt.shiftKey));
@@ -483,12 +497,40 @@ export class InputController {
// 글 단계(TEXT·MTEXT)는 친 그대로 — 명령 입력일 때만 대문자로 바꾼다(보이는 글만 · 맞추기는 prompt.ts).
// Ctrl · Alt · ⌘ 와 함께 누른 글자는 쌓지 않음(Ctrl+C → 원이 되던 것).
this.text += activeToolWantsFreeText() ? evt.key : evt.key.toUpperCase();
this.browsing = null;
}
}
/** ↑↓ — 후보 보일 때 = 후보 돌림 · 아니면 기록(쉼 = 친 명령 · 명령 중 = 그 물음 최근 값) */
private recall(dir: -1 | 1) {
const candidatesShown = isIdle() && !!this.text && !!commandCandidate(this.text, 0);
if (this.browsing === 'candidates' || (this.browsing === null && candidatesShown)) {
this.cycleCandidate(dir);
return;
}
const value = recallInput(dir, this.text);
if (value === null) return;
this.text = value;
this.browsing = 'history';
}
/** 명령 후보 돌림 — 처음 친 글로 시작하는 명령 이름을 차례로 친 글 자리에 */
private cycleCandidate(dir: -1 | 1) {
if (this.browsing !== 'candidates') {
this.candidateBase = this.text;
this.candidateIndex = dir > 0 ? -1 : 0;
}
const name = commandCandidate(this.candidateBase, this.candidateIndex + dir);
if (!name) return;
this.candidateIndex += dir;
this.text = name;
this.browsing = 'candidates';
}
/** Esc 한 번 = 명령 끝(오토캐드) — 친 글 · 조합 · 각도 잠금 · 한 번 스냅 · 덧물음도 함께 풂 */
public handleEscapeKey() {
this.text = '';
this.browsing = null;
this.ime.composing = '';
const boxing =
this.isSelectToolActive() &&
@@ -511,7 +553,12 @@ export class InputController {
if (this.text === '' || !isIdle()) {
return [];
}
return matchCommandPrefixes(this.text).slice(0, 6).map(describeCommand);
const picking = this.browsing === 'candidates';
const candidates = matchCommandPrefixes(picking ? this.candidateBase : this.text);
const picked = picking ? candidates.findIndex((command) => command.id === this.text) : -1;
return candidates
.slice(0, 6)
.map((command, index) => `${index === picked ? '▸ ' : ''}${describeCommand(command)}`);
}
/**
@@ -521,6 +568,8 @@ export class InputController {
public handleEnterKey(_repeatIfIdle = false) {
const text = this.text;
this.text = '';
this.browsing = null;
pushInput(text); // ↑↓ 기록(지금 물음 묶음 — 해석 전에)
routeTyped(text);
}