LCOV - code coverage report
Current view: top level - dashql-app/src/shell - completion_overlay.ts Coverage Total Hit
Test: DashQL d805a4b Lines: 95.8 % 72 69
Test Date: 2026-11-09 20:22:13

            Line data    Source code
       1              : import type * as dashql from '@ankoh/dashql-core';
       2              : import type { Terminal } from '@xterm/xterm';
       3              : 
       4              : import type { DashQLShell } from './api.js';
       5              : import { CandidateListRenderer, collectCandidates, placeBelowOrAbove } from '../views/completion_list/completion_list_view.js';
       6              : 
       7              : import * as styles from '../views/completion_list/completion_list.module.css';
       8              : 
       9              : /// The completion list of a shell, over the terminal.
      10              : /// The shell owns the candidates, the selection and the keys, this only shows what the shell says.
      11            1 : export class TerminalCompletionOverlay {
      12              :     /// The list
      13            1 :     readonly list: CandidateListRenderer;
      14              :     /// The candidates of the shell
      15            1 :     buffer: dashql.FlatBufferPtr<dashql.buffers.completion.Completion> | null = null;
      16              :     /// The revision of the candidates
      17            1 :     revision: bigint | null = null;
      18              :     /// The terminal column of the start of the replaced text
      19            1 :     anchorColumn: number = 0;
      20              :     /// The anchor that the list is placed at, null if it has to be placed
      21            1 :     placedAt: string | null = null;
      22              : 
      23            1 :     constructor(
      24            1 :         readonly container: HTMLElement,
      25            1 :         readonly terminal: Terminal,
      26            1 :         selectPreviousVariant: () => void,
      27            1 :         selectNextVariant: () => void,
      28            1 :     ) {
      29            1 :         this.list = new CandidateListRenderer(selectPreviousVariant, selectNextVariant);
      30              :         // The terminal is dark in both schemes
      31            1 :         this.list.rootElement.classList.add(styles.on_dark_surface);
      32              :         // Inside the container and not on the body: the list has to hide with a page that goes to the background
      33            1 :         container.appendChild(this.list.rootElement);
      34            1 :     }
      35              : 
      36              :     /// Is a completion list open? It can still be hidden if its row is scrolled out of the terminal.
      37            1 :     public get isOpen() { return this.buffer != null; }
      38              : 
      39              :     /// Show what the shell says, after the terminal processed the output of the shell
      40            1 :     public update(shell: DashQLShell) {
      41            1 :         const state = shell.terminalCompletionList();
      42            1 :         if (state == null) {
      43            1 :             this.close();
      44            1 :             return;
      45            1 :         }
      46            1 :         if (this.buffer == null || this.revision !== state.revision) {
      47            1 :             this.buffer = shell.terminalCompletionBuffer();
      48            1 :             this.revision = state.revision;
      49            1 :         }
      50            1 :         if (this.buffer == null) {
      51            0 :             this.close();
      52            0 :             return;
      53            0 :         }
      54            1 :         this.anchorColumn = state.anchorColumn;
      55            1 :         this.list.show();
      56            1 :         this.list.updateCandidates(collectCandidates(this.buffer.read(), state.selection, state.variant));
      57              :         // The width of the list can differ now
      58            1 :         this.placedAt = null;
      59            1 :         this.place();
      60            1 :     }
      61              : 
      62              :     /// Place the list at its anchor, if the terminal moved it
      63            1 :     public place() {
      64            1 :         if (!this.isOpen) return;
      65            1 :         const screen = this.terminal.element?.querySelector('.xterm-screen');
      66            1 :         if (screen == null) return;
      67            1 :         const rect = screen.getBoundingClientRect();
      68            1 :         const { cols, rows, buffer: { active } } = this.terminal;
      69            1 :         const row = active.baseY + active.cursorY - active.viewportY;
      70            1 :         if (rect.width === 0 || row < 0 || row >= rows) {
      71            1 :             this.list.hide();
      72            1 :             this.placedAt = null;
      73            1 :             return;
      74            1 :         }
      75            1 :         const placedAt = `${rect.left} ${rect.top} ${rect.width} ${rect.height} ${row} ${cols} ${rows} ${this.anchorColumn}`;
      76            1 :         if (placedAt === this.placedAt) return;
      77            1 :         this.placedAt = placedAt;
      78            1 :         const cellWidth = rect.width / cols;
      79            1 :         const cellHeight = rect.height / rows;
      80            1 :         this.list.show();
      81            1 :         this.list.updatePosition(this.list.fitIntoWindow(placeBelowOrAbove({
      82            1 :             left: rect.left + this.anchorColumn * cellWidth,
      83            1 :             top: rect.top + row * cellHeight,
      84            1 :             bottom: rect.top + (row + 1) * cellHeight,
      85            1 :         })));
      86            1 :     }
      87              : 
      88              :     /// Close the list
      89            1 :     public close() {
      90            1 :         this.buffer = null;
      91            1 :         this.revision = null;
      92            1 :         this.placedAt = null;
      93            1 :         this.list.hide();
      94            1 :     }
      95              : 
      96            1 :     public destroy() {
      97            1 :         this.close();
      98            1 :         this.list.destroy();
      99            1 :     }
     100            1 : }
        

Generated by: LCOV version 2.0-1