irongit

ui: terminal prompt grows to six lines, Shift+Enter adds a line

huncholanehuncholaneauthored
parent afdc6cecommit e752aba553fde33fa07184e405413b40a31a2a9eBrowse files

3 files changed, +56 -16

+29-9src/lib/gcs/console.ts
@@ -57,7 +57,7 @@ export function initConsole() {
5757 const log = panel.querySelector<HTMLElement>('[data-term-log]')!;
5858 const welcome = panel.querySelector<HTMLElement>('[data-term-welcome]')!;
5959 const form = panel.querySelector<HTMLFormElement>('[data-term-form]')!;
60- const input = panel.querySelector<HTMLInputElement>('#termInput')!;
60+ const input = panel.querySelector<HTMLTextAreaElement>('#termInput')!;
6161 const menu = panel.querySelector<HTMLElement>('[data-term-menu]')!;
6262 const hints = panel.querySelector<HTMLElement>('[data-term-hints]')!;
6363 const launchers = Array.from(document.querySelectorAll<HTMLElement>('[data-term-open]'));
@@ -149,6 +149,17 @@ export function initConsole() {
149149
150150 const isOpen = () => !panel.hidden;
151151
152+ // The prompt grows with its text up to about six lines, then scrolls.
153+ const fit = () => {
154+ input.style.height = 'auto';
155+ input.style.height = `${input.scrollHeight}px`;
156+ };
157+ const setValue = (v: string) => {
158+ input.value = v;
159+ fit();
160+ input.setSelectionRange(v.length, v.length);
161+ };
162+
152163 const commands: Record<string, Command> = {
153164 clear: {
154165 help: 'start a new conversation',
@@ -358,12 +369,12 @@ export function initConsole() {
358369 const [name, c] = matches[i] ?? [];
359370 if (!name) return;
360371 if (c!.usage || !run) {
361- input.value = `/${name}${c!.usage ? ' ' : ''}`;
372+ setValue(`/${name}${c!.usage ? ' ' : ''}`);
362373 hideMenu();
363374 if (!c!.usage) updateMenu();
364375 return;
365376 }
366- input.value = '';
377+ setValue('');
367378 hideMenu();
368379 submit(`/${name}`, 'menu');
369380 }
@@ -399,33 +410,42 @@ export function initConsole() {
399410 return pick(menuIndex, true);
400411 }
401412 const raw = input.value;
402- input.value = '';
413+ setValue('');
403414 hideMenu();
404415 submit(raw);
405416 });
406417
407418 input.addEventListener('input', () => {
408419 menuIndex = 0;
420+ fit();
409421 updateMenu();
410422 });
411423
424+ // Enter sends, Shift+Enter adds a line. Up and Down walk the history only
425+ // from the first or last line, so they still move the cursor in long text.
412426 input.addEventListener('keydown', (e) => {
413427 const menuOpen = !menu.hidden && matches.length > 0;
414- if (e.key === 'Tab' && menuOpen) {
428+ const caret = input.selectionStart ?? 0;
429+ const onFirstLine = !input.value.slice(0, caret).includes('\n');
430+ const onLastLine = !input.value.slice(input.selectionEnd ?? caret).includes('\n');
431+ if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
432+ e.preventDefault();
433+ form.requestSubmit();
434+ } else if (e.key === 'Tab' && menuOpen) {
415435 e.preventDefault();
416436 pick(menuIndex, false);
417437 } else if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && menuOpen) {
418438 e.preventDefault();
419439 menuIndex = (menuIndex + (e.key === 'ArrowUp' ? -1 : 1) + matches.length) % matches.length;
420440 updateMenu();
421- } else if (e.key === 'ArrowUp' && inputHistory.length) {
441+ } else if (e.key === 'ArrowUp' && onFirstLine && inputHistory.length) {
422442 e.preventDefault();
423443 hIndex = Math.min(inputHistory.length - 1, hIndex + 1);
424- input.value = inputHistory[hIndex];
425- } else if (e.key === 'ArrowDown' && hIndex > -1) {
444+ setValue(inputHistory[hIndex]);
445+ } else if (e.key === 'ArrowDown' && onLastLine && hIndex > -1) {
426446 e.preventDefault();
427447 hIndex -= 1;
428- input.value = hIndex === -1 ? '' : inputHistory[hIndex];
448+ setValue(hIndex === -1 ? '' : inputHistory[hIndex]);
429449 }
430450 });
431451
+5-5src/pages/index.astro
@@ -625,21 +625,21 @@ const pad = (n: number) => String(n).padStart(2, '0');
625625 <form class="term-form" data-term-form>
626626 <label class="term-input" for="termInput">
627627 <span class="text-vol" aria-hidden="true">&gt;</span>
628- <input
628+ <textarea
629629 id="termInput"
630- class="min-w-0 flex-1 bg-transparent caret-vol outline-none placeholder:text-muted/70"
630+ class="term-textarea"
631+ rows="1"
631632 autocomplete="off"
632633 autocapitalize="off"
633634 spellcheck="false"
634635 enterkeyhint="send"
635636 maxlength="600"
636637 placeholder="Ask about Lane, or / for commands"
637- aria-label="Ask about Lane, or type a slash command"
638- />
638+ aria-label="Ask about Lane, or type a slash command. Shift+Enter adds a line."></textarea>
639639 </label>
640640 <div class="term-menu" data-term-menu role="listbox" aria-label="Commands" hidden></div>
641641 <div class="term-hints" data-term-hints>
642- <span>/ commands &nbsp; ↑ history &nbsp; esc close</span>
642+ <span>/ commands &nbsp; <span class="term-hint-wide">shift+enter new line &nbsp; </span>↑ history &nbsp; esc close</span>
643643 </div>
644644 </form>
645645 </section>
+22-2src/styles/site.css
@@ -323,13 +323,32 @@
323323 }
324324 .term-input {
325325 display: flex;
326- align-items: center;
326+ align-items: flex-start;
327327 gap: 0.6rem;
328328 padding: 0.4rem 0.6rem;
329329 border: 1px solid var(--color-rock);
330330 border-radius: 4px;
331331 cursor: text;
332332 }
333+.term-textarea {
334+ display: block;
335+ flex: 1;
336+ min-width: 0;
337+ max-height: calc(6 * 1.55em);
338+ padding: 0;
339+ overflow-y: auto;
340+ resize: none;
341+ border: 0;
342+ background: transparent;
343+ font: inherit;
344+ line-height: 1.55;
345+ color: inherit;
346+ caret-color: var(--color-vol);
347+ outline: none;
348+}
349+.term-textarea::placeholder {
350+ color: rgb(143 145 148 / 0.7);
351+}
333352 .term-input:focus-within {
334353 border-color: rgb(255 130 0 / 0.6);
335354 }
@@ -360,7 +379,8 @@
360379 color: var(--color-vol-ink);
361380 }
362381 @media (max-width: 639px) {
363- .term-kbd {
382+ .term-kbd,
383+ .term-hint-wide {
364384 display: none;
365385 }
366386 }