@code-yeongyu/senpi-tui 2026.9.29 → 2026.10.1-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/autocomplete.d.ts +5 -0
- package/dist/autocomplete.js +38 -14
- package/dist/colors.d.ts +73 -0
- package/dist/colors.js +273 -0
- package/dist/components/box.js +5 -3
- package/dist/components/editor.d.ts +4 -1
- package/dist/components/editor.js +23 -8
- package/dist/components/latex.js +5 -0
- package/dist/editor-component.d.ts +7 -2
- package/dist/fuzzy.js +23 -22
- package/dist/index.d.ts +6 -3
- package/dist/index.js +5 -2
- package/dist/native-platform.d.ts +2 -0
- package/dist/oklab.d.ts +43 -0
- package/dist/oklab.js +210 -0
- package/dist/slash-command-autocomplete.js +3 -0
- package/dist/terminal-capabilities.js +1 -1
- package/dist/terminal-colors.d.ts +19 -2
- package/dist/terminal-colors.js +12 -7
- package/dist/terminal-image.d.ts +3 -1
- package/dist/terminal-image.js +33 -9
- package/dist/terminal.d.ts +3 -0
- package/dist/terminal.js +15 -8
- package/dist/tui-alt-screen.d.ts +12 -7
- package/dist/tui-alt-screen.js +37 -17
- package/dist/tui.d.ts +18 -17
- package/dist/tui.js +79 -62
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +108 -60
- package/dist/wheel-scroll.d.ts +23 -0
- package/dist/wheel-scroll.js +70 -0
- package/native/clipboard.h +40 -2
- package/native/darwin/README.md +1 -1
- package/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/native/darwin/src/darwin-platform.m +39 -0
- package/native/napi.h +2 -0
- package/package.json +3 -2
- package/dist/alt-screen-search.d.ts.map +0 -1
- package/dist/alt-screen-search.js.map +0 -1
- package/dist/autocomplete.d.ts.map +0 -1
- package/dist/autocomplete.js.map +0 -1
- package/dist/components/alt-screen-flash.d.ts.map +0 -1
- package/dist/components/alt-screen-flash.js.map +0 -1
- package/dist/components/box.d.ts.map +0 -1
- package/dist/components/box.js.map +0 -1
- package/dist/components/cancellable-loader.d.ts.map +0 -1
- package/dist/components/cancellable-loader.js.map +0 -1
- package/dist/components/editor-line-render.d.ts.map +0 -1
- package/dist/components/editor-line-render.js.map +0 -1
- package/dist/components/editor.d.ts.map +0 -1
- package/dist/components/editor.js.map +0 -1
- package/dist/components/h-stack.d.ts.map +0 -1
- package/dist/components/h-stack.js.map +0 -1
- package/dist/components/image.d.ts.map +0 -1
- package/dist/components/image.js.map +0 -1
- package/dist/components/input.d.ts.map +0 -1
- package/dist/components/input.js.map +0 -1
- package/dist/components/latex.d.ts.map +0 -1
- package/dist/components/latex.js.map +0 -1
- package/dist/components/loader.d.ts.map +0 -1
- package/dist/components/loader.js.map +0 -1
- package/dist/components/markdown.d.ts.map +0 -1
- package/dist/components/markdown.js.map +0 -1
- package/dist/components/mouse-region.d.ts.map +0 -1
- package/dist/components/mouse-region.js.map +0 -1
- package/dist/components/scroll-view.d.ts.map +0 -1
- package/dist/components/scroll-view.js.map +0 -1
- package/dist/components/select-list.d.ts.map +0 -1
- package/dist/components/select-list.js.map +0 -1
- package/dist/components/settings-list.d.ts.map +0 -1
- package/dist/components/settings-list.js.map +0 -1
- package/dist/components/spacer.d.ts.map +0 -1
- package/dist/components/spacer.js.map +0 -1
- package/dist/components/stack.d.ts.map +0 -1
- package/dist/components/stack.js.map +0 -1
- package/dist/components/text.d.ts.map +0 -1
- package/dist/components/text.js.map +0 -1
- package/dist/components/truncated-text.d.ts.map +0 -1
- package/dist/components/truncated-text.js.map +0 -1
- package/dist/components/v-stack.d.ts.map +0 -1
- package/dist/components/v-stack.js.map +0 -1
- package/dist/dollar-invocation-autocomplete.d.ts.map +0 -1
- package/dist/dollar-invocation-autocomplete.js.map +0 -1
- package/dist/editor-component.d.ts.map +0 -1
- package/dist/editor-component.js.map +0 -1
- package/dist/fuzzy.d.ts.map +0 -1
- package/dist/fuzzy.js.map +0 -1
- package/dist/image-markers.d.ts.map +0 -1
- package/dist/image-markers.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/keybindings.d.ts.map +0 -1
- package/dist/keybindings.js.map +0 -1
- package/dist/keys.d.ts.map +0 -1
- package/dist/keys.js.map +0 -1
- package/dist/kill-ring.d.ts.map +0 -1
- package/dist/kill-ring.js.map +0 -1
- package/dist/layout-node.d.ts.map +0 -1
- package/dist/layout-node.js.map +0 -1
- package/dist/layout.d.ts.map +0 -1
- package/dist/layout.js.map +0 -1
- package/dist/mouse-input.d.ts.map +0 -1
- package/dist/mouse-input.js.map +0 -1
- package/dist/mux.d.ts.map +0 -1
- package/dist/mux.js.map +0 -1
- package/dist/native-modifiers.d.ts.map +0 -1
- package/dist/native-modifiers.js.map +0 -1
- package/dist/native-module-path.d.ts.map +0 -1
- package/dist/native-module-path.js.map +0 -1
- package/dist/native-platform.d.ts.map +0 -1
- package/dist/native-platform.js.map +0 -1
- package/dist/paste-markers.d.ts.map +0 -1
- package/dist/paste-markers.js.map +0 -1
- package/dist/slash-command-autocomplete.d.ts.map +0 -1
- package/dist/slash-command-autocomplete.js.map +0 -1
- package/dist/stderr-observer.d.ts.map +0 -1
- package/dist/stderr-observer.js.map +0 -1
- package/dist/stdin-buffer.d.ts.map +0 -1
- package/dist/stdin-buffer.js.map +0 -1
- package/dist/terminal-capabilities.d.ts.map +0 -1
- package/dist/terminal-capabilities.js.map +0 -1
- package/dist/terminal-colors.d.ts.map +0 -1
- package/dist/terminal-colors.js.map +0 -1
- package/dist/terminal-image.d.ts.map +0 -1
- package/dist/terminal-image.js.map +0 -1
- package/dist/terminal-text.d.ts.map +0 -1
- package/dist/terminal-text.js.map +0 -1
- package/dist/terminal.d.ts.map +0 -1
- package/dist/terminal.js.map +0 -1
- package/dist/tmux-cursor-query.d.ts.map +0 -1
- package/dist/tmux-cursor-query.js.map +0 -1
- package/dist/tmux-focus.d.ts.map +0 -1
- package/dist/tmux-focus.js.map +0 -1
- package/dist/tmux-image-capability.d.ts.map +0 -1
- package/dist/tmux-image-capability.js.map +0 -1
- package/dist/tmux-image-probe.d.ts.map +0 -1
- package/dist/tmux-image-probe.js.map +0 -1
- package/dist/tui-alt-screen.d.ts.map +0 -1
- package/dist/tui-alt-screen.js.map +0 -1
- package/dist/tui-main-screen.d.ts.map +0 -1
- package/dist/tui-main-screen.js.map +0 -1
- package/dist/tui.d.ts.map +0 -1
- package/dist/tui.js.map +0 -1
- package/dist/undo-stack.d.ts.map +0 -1
- package/dist/undo-stack.js.map +0 -1
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js.map +0 -1
- package/dist/word-navigation.d.ts.map +0 -1
- package/dist/word-navigation.js.map +0 -1
package/dist/components/latex.js
CHANGED
|
@@ -134,6 +134,7 @@ const MAX_FORMULA_LENGTH = 4096;
|
|
|
134
134
|
const MAX_NESTING_DEPTH = 64;
|
|
135
135
|
const LEADING_COMBINING_MARK_REGEX = /^\p{Mark}/u;
|
|
136
136
|
const STYLE_COMMANDS = new Set(["\\mathrm", "\\mathbf", "\\mathit", "\\text", "\\operatorname"]);
|
|
137
|
+
const FONT_SWITCH_COMMANDS = new Set(["\\bf", "\\cal", "\\it", "\\rm", "\\sf", "\\sl", "\\tt"]);
|
|
137
138
|
const scriptText = (text, alphabet) => {
|
|
138
139
|
let output = "";
|
|
139
140
|
for (const character of text) {
|
|
@@ -219,6 +220,10 @@ class LatexParser {
|
|
|
219
220
|
const symbol = SYMBOLS[command];
|
|
220
221
|
if (symbol !== undefined)
|
|
221
222
|
return symbol;
|
|
223
|
+
if (FONT_SWITCH_COMMANDS.has(command)) {
|
|
224
|
+
this.skipSpaces();
|
|
225
|
+
return "";
|
|
226
|
+
}
|
|
222
227
|
if (STYLE_COMMANDS.has(command)) {
|
|
223
228
|
this.skipSpaces();
|
|
224
229
|
return this.input[this.index] === "{" ? this.parseGroup(depth) : command;
|
|
@@ -2,6 +2,11 @@ import type { AutocompleteProvider } from "./autocomplete.ts";
|
|
|
2
2
|
import type { EditorImageState } from "./image-markers.ts";
|
|
3
3
|
import type { EditorPasteState } from "./paste-markers.ts";
|
|
4
4
|
import type { Component } from "./tui.ts";
|
|
5
|
+
/** What a submission carried before the editor trimmed it for `onSubmit`. */
|
|
6
|
+
export interface EditorSubmitDetails {
|
|
7
|
+
/** The submitted text with paste markers expanded, before trimming. */
|
|
8
|
+
readonly rawText: string;
|
|
9
|
+
}
|
|
5
10
|
/**
|
|
6
11
|
* Interface for custom editor components.
|
|
7
12
|
*
|
|
@@ -16,8 +21,8 @@ export interface EditorComponent extends Component {
|
|
|
16
21
|
setText(text: string): void;
|
|
17
22
|
/** Handle raw terminal input (key presses, paste sequences, etc.) */
|
|
18
23
|
handleInput(data: string): void;
|
|
19
|
-
/** Called when user submits (e.g., Enter key) */
|
|
20
|
-
onSubmit?: (text: string) => void;
|
|
24
|
+
/** Called when user submits (e.g., Enter key); `details` is absent when an editor does not report it. */
|
|
25
|
+
onSubmit?: (text: string, details?: EditorSubmitDetails) => void;
|
|
21
26
|
/** Called when text changes */
|
|
22
27
|
onChange?: (text: string) => void;
|
|
23
28
|
/** Add text to history for up/down navigation */
|
package/dist/fuzzy.js
CHANGED
|
@@ -45,30 +45,31 @@ function scoreMatch(queryLower, textLower) {
|
|
|
45
45
|
let score = 0;
|
|
46
46
|
let lastMatchIndex = -1;
|
|
47
47
|
let consecutiveMatches = 0;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// Reward word boundary matches
|
|
64
|
-
if (isWordBoundary) {
|
|
65
|
-
score -= 10;
|
|
48
|
+
while (queryIndex < queryLower.length) {
|
|
49
|
+
const i = textLower.indexOf(queryLower[queryIndex], lastMatchIndex + 1);
|
|
50
|
+
if (i === -1)
|
|
51
|
+
break;
|
|
52
|
+
const isWordBoundary = i === 0 || isWordBoundaryPrefix(textLower[i - 1]);
|
|
53
|
+
// Reward consecutive matches
|
|
54
|
+
if (lastMatchIndex === i - 1) {
|
|
55
|
+
consecutiveMatches++;
|
|
56
|
+
score -= consecutiveMatches * 5;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
consecutiveMatches = 0;
|
|
60
|
+
// Penalize gaps
|
|
61
|
+
if (lastMatchIndex >= 0) {
|
|
62
|
+
score += (i - lastMatchIndex - 1) * 2;
|
|
66
63
|
}
|
|
67
|
-
// Slight penalty for later matches
|
|
68
|
-
score += i * 0.1;
|
|
69
|
-
lastMatchIndex = i;
|
|
70
|
-
queryIndex++;
|
|
71
64
|
}
|
|
65
|
+
// Reward word boundary matches
|
|
66
|
+
if (isWordBoundary) {
|
|
67
|
+
score -= 10;
|
|
68
|
+
}
|
|
69
|
+
// Slight penalty for later matches
|
|
70
|
+
score += i * 0.1;
|
|
71
|
+
lastMatchIndex = i;
|
|
72
|
+
queryIndex++;
|
|
72
73
|
}
|
|
73
74
|
if (queryIndex < queryLower.length) {
|
|
74
75
|
return { matches: false, score: 0 };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { CellDimensions, ImageDimensions } from "./terminal-image.ts";
|
|
|
2
2
|
export { Marked, type Token, type Tokens } from "marked";
|
|
3
3
|
export { AltScreenSearchComponent, type AltScreenSearchMatch, type AltScreenSearchSegment, findAltScreenSearchMatches, getAltScreenSearchMatchKey, } from "./alt-screen-search.ts";
|
|
4
4
|
export { type AutocompleteItem, type AutocompleteProvider, type AutocompleteSuggestions, CombinedAutocompleteProvider, type SlashCommand, } from "./autocomplete.ts";
|
|
5
|
+
export { backgroundAnsi, type Color, type ColorMixSpace, colorToHex, colorToOkhsl, colorToOklch, colorToRgb, foregroundAnsi, type IndexedColor, indexedColor, mixColors, type OkhslChannels, type OklchChannels, type OklchColorValue, okhslColor, oklchColor, parseColor, type RgbColorValue, rgbColor, styleText, styleTextWithAnsi, type TerminalColorMode, type TextAttributes, type TextStyle, } from "./colors.ts";
|
|
5
6
|
export { Box } from "./components/box.ts";
|
|
6
7
|
export { CancellableLoader } from "./components/cancellable-loader.ts";
|
|
7
8
|
export { Editor, type EditorOptions, type EditorTheme, } from "./components/editor.ts";
|
|
@@ -18,22 +19,24 @@ export { Spacer } from "./components/spacer.ts";
|
|
|
18
19
|
export { Text } from "./components/text.ts";
|
|
19
20
|
export { TruncatedText } from "./components/truncated-text.ts";
|
|
20
21
|
export { type StackChild, type StackEntry, type StackEntryOptions, type StackOptions, VStack, } from "./components/v-stack.ts";
|
|
21
|
-
export type { EditorComponent } from "./editor-component.ts";
|
|
22
|
+
export type { EditorComponent, EditorSubmitDetails } from "./editor-component.ts";
|
|
22
23
|
export { type FuzzyMatch, fuzzyFilter, fuzzyMatch, fuzzyMatchLower } from "./fuzzy.ts";
|
|
23
24
|
export { type EditorImageState, formatImageMarker, IMAGE_MARKER_REGEX, IMAGE_MARKER_SINGLE, type ImageMarkerCanonicalization, ImageMarkerRegistry, type ImageMarkerRemoval, imageMarkerId, isImageMarker, } from "./image-markers.ts";
|
|
24
25
|
export { getKeybindings, type Keybinding, type KeybindingConflict, type KeybindingDefinition, type KeybindingDefinitions, type Keybindings, type KeybindingsConfig, KeybindingsManager, setKeybindings, TUI_KEYBINDINGS, } from "./keybindings.ts";
|
|
25
26
|
export { decodeKittyPrintable, isKeyRelease, isKeyRepeat, isKittyProtocolActive, Key, type KeyEventType, type KeyId, matchesKey, parseKey, setKittyProtocolActive, } from "./keys.ts";
|
|
26
27
|
export { decodeMouseButton, isMouseSequence, MOUSE_TRACKING, MouseClickSynthesizer, parseSgrMouseEvent, parseWheelEvent, type SgrMouseEvent, toTuiMouseEvent, type WheelEvent, } from "./mouse-input.ts";
|
|
27
28
|
export { getNativeClipboard, type NativeClipboard } from "./native-platform.ts";
|
|
29
|
+
export { oklabToOkhslLightness } from "./oklab.ts";
|
|
28
30
|
export { type EditorPasteState, expandPasteMarkers } from "./paste-markers.ts";
|
|
29
31
|
export { StdinBuffer, type StdinBufferEventMap, type StdinBufferOptions } from "./stdin-buffer.ts";
|
|
30
32
|
export { type CursorPosition, ProcessTerminal, type ProcessTerminalOptions, type Terminal } from "./terminal.ts";
|
|
31
|
-
export {
|
|
33
|
+
export { parseTerminalColorSchemeReport, type RgbColor, type TerminalColorScheme, type TerminalColors, } from "./terminal-colors.ts";
|
|
32
34
|
export declare function calculateImageRows(imageDimensions: ImageDimensions, targetWidthCells: number, cellDimensions?: CellDimensions): number;
|
|
33
|
-
export { allocateImageId, buildKittyPlaceholderRow, type CellDimensions, deleteAllKittyImages, deleteKittyImage, detectCapabilities, encodeITerm2, encodeKitty, getCapabilities, getCellDimensions, getGifDimensions, getImageDimensions, getJpegDimensions, getPngDimensions, getWebpDimensions, hyperlink, type ImageDimensions, type ImageProtocol, type ImageRenderOptions, imageFallback, KITTY_PLACEHOLDER_MAX, outerKittyGraphicsMode, renderImage, resetCapabilitiesCache, setCapabilities, setCapabilityOverrides, setCellDimensions, type TerminalCapabilities, type TmuxPassthroughState, wrapTmuxPassthrough, } from "./terminal-image.ts";
|
|
35
|
+
export { allocateImageId, buildKittyPlaceholderRow, type CellDimensions, deleteAllKittyImages, deleteKittyImage, detectCapabilities, encodeITerm2, encodeKitty, getCapabilities, getCellDimensions, getGifDimensions, getImageDimensions, getJpegDimensions, getPngDimensions, getTerminalColorMode, getWebpDimensions, hyperlink, type ImageDimensions, type ImageProtocol, type ImageRenderOptions, imageFallback, KITTY_PLACEHOLDER_MAX, outerKittyGraphicsMode, renderImage, resetCapabilitiesCache, setCapabilities, setCapabilityOverrides, setCellDimensions, type TerminalCapabilities, type TmuxPassthroughState, wrapTmuxPassthrough, } from "./terminal-image.ts";
|
|
34
36
|
export { sanitizeTerminalLabel, shortenImagePath } from "./terminal-text.ts";
|
|
35
37
|
export { type Component, Container, CURSOR_MARKER, compositeTuiLine, type Focusable, isFocusable, isViewportTUI, type OverlayAnchor, type OverlayBounds, type OverlayHandle, type OverlayMargin, type OverlayOptions, type OverlayUnfocusOptions, type SizeValue, TUI, type TuiInputListener, type TuiInputListenerResult, type TuiMode, type TuiMouseButton, type TuiMouseEvent, type TuiMouseEventResult, type TuiMouseEventType, type TuiStopOptions, type ViewportTUI, } from "./tui.ts";
|
|
36
38
|
export { TuiAltScreen, type TuiAltScreenOptions } from "./tui-alt-screen.ts";
|
|
37
39
|
export { TuiMainScreen, type TuiMainScreenRenderState } from "./tui-main-screen.ts";
|
|
38
40
|
export { getGraphemeSegmenter, getOsc8LinkAtColumn, getWordSegmenter, sliceByColumn, stripTerminalSequences, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "./utils.ts";
|
|
41
|
+
export type { WheelScrollLines } from "./wheel-scroll.ts";
|
|
39
42
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ export { Marked } from "marked";
|
|
|
5
5
|
export { AltScreenSearchComponent, findAltScreenSearchMatches, getAltScreenSearchMatchKey, } from "./alt-screen-search.js";
|
|
6
6
|
// Autocomplete support
|
|
7
7
|
export { CombinedAutocompleteProvider, } from "./autocomplete.js";
|
|
8
|
+
// Colors and styling
|
|
9
|
+
export { backgroundAnsi, colorToHex, colorToOkhsl, colorToOklch, colorToRgb, foregroundAnsi, indexedColor, mixColors, okhslColor, oklchColor, parseColor, rgbColor, styleText, styleTextWithAnsi, } from "./colors.js";
|
|
8
10
|
// Components
|
|
9
11
|
export { Box } from "./components/box.js";
|
|
10
12
|
export { CancellableLoader } from "./components/cancellable-loader.js";
|
|
@@ -33,18 +35,19 @@ export { decodeKittyPrintable, isKeyRelease, isKeyRepeat, isKittyProtocolActive,
|
|
|
33
35
|
export { decodeMouseButton, isMouseSequence, MOUSE_TRACKING, MouseClickSynthesizer, parseSgrMouseEvent, parseWheelEvent, toTuiMouseEvent, } from "./mouse-input.js";
|
|
34
36
|
// Native platform integration
|
|
35
37
|
export { getNativeClipboard } from "./native-platform.js";
|
|
38
|
+
export { oklabToOkhslLightness } from "./oklab.js";
|
|
36
39
|
export { expandPasteMarkers } from "./paste-markers.js";
|
|
37
40
|
// Input buffering for batch splitting
|
|
38
41
|
export { StdinBuffer } from "./stdin-buffer.js";
|
|
39
42
|
// Terminal interface and implementations
|
|
40
43
|
export { ProcessTerminal } from "./terminal.js";
|
|
41
44
|
// Terminal colors
|
|
42
|
-
export {
|
|
45
|
+
export { parseTerminalColorSchemeReport, } from "./terminal-colors.js";
|
|
43
46
|
// Terminal image support
|
|
44
47
|
export function calculateImageRows(imageDimensions, targetWidthCells, cellDimensions = { widthPx: 9, heightPx: 18 }) {
|
|
45
48
|
return calculateImageCellSize(imageDimensions, targetWidthCells, undefined, cellDimensions).rows;
|
|
46
49
|
}
|
|
47
|
-
export { allocateImageId, buildKittyPlaceholderRow, deleteAllKittyImages, deleteKittyImage, detectCapabilities, encodeITerm2, encodeKitty, getCapabilities, getCellDimensions, getGifDimensions, getImageDimensions, getJpegDimensions, getPngDimensions, getWebpDimensions, hyperlink, imageFallback, KITTY_PLACEHOLDER_MAX, outerKittyGraphicsMode, renderImage, resetCapabilitiesCache, setCapabilities, setCapabilityOverrides, setCellDimensions, wrapTmuxPassthrough, } from "./terminal-image.js";
|
|
50
|
+
export { allocateImageId, buildKittyPlaceholderRow, deleteAllKittyImages, deleteKittyImage, detectCapabilities, encodeITerm2, encodeKitty, getCapabilities, getCellDimensions, getGifDimensions, getImageDimensions, getJpegDimensions, getPngDimensions, getTerminalColorMode, getWebpDimensions, hyperlink, imageFallback, KITTY_PLACEHOLDER_MAX, outerKittyGraphicsMode, renderImage, resetCapabilitiesCache, setCapabilities, setCapabilityOverrides, setCellDimensions, wrapTmuxPassthrough, } from "./terminal-image.js";
|
|
48
51
|
export { sanitizeTerminalLabel, shortenImagePath } from "./terminal-text.js";
|
|
49
52
|
export { Container, CURSOR_MARKER, compositeTuiLine, isFocusable, isViewportTUI, TUI, } from "./tui.js";
|
|
50
53
|
export { TuiAltScreen } from "./tui-alt-screen.js";
|
|
@@ -4,6 +4,8 @@ export interface NativeClipboard {
|
|
|
4
4
|
getText(): Promise<string | null | undefined>;
|
|
5
5
|
/** Undefined means unavailable, null means no image; transfer failures reject. */
|
|
6
6
|
getImage(): Promise<Uint8Array | null | undefined>;
|
|
7
|
+
/** POSIX paths of file URLs on the clipboard. Undefined means unsupported, null means no files. */
|
|
8
|
+
getFilePaths?(): Promise<string[] | null | undefined>;
|
|
7
9
|
/** Linux uses command-line tools to retain clipboard ownership instead. */
|
|
8
10
|
setText?(text: string): Promise<void>;
|
|
9
11
|
}
|
package/dist/oklab.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oklab and OKHSL <-> sRGB conversion. `colors.ts` builds its OKLCH, OKHSL, and color mixing on it.
|
|
3
|
+
*
|
|
4
|
+
* Oklab and OKHSL are Björn Ottosson's color spaces; OKHSL's saturation is relative to the sRGB gamut at
|
|
5
|
+
* each hue and lightness. This is a port of his reference implementation (https://bottosson.github.io/posts/colorpicker/),
|
|
6
|
+
* Copyright (c) 2021 Björn Ottosson, used under the MIT license:
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
9
|
+
* associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
10
|
+
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
12
|
+
* following conditions: The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
* copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
14
|
+
* KIND, EXPRESS OR IMPLIED.
|
|
15
|
+
*/
|
|
16
|
+
import type { RgbColor } from "./terminal-colors.ts";
|
|
17
|
+
type Vector = [number, number, number];
|
|
18
|
+
/** Oklab lightness to OKHSL lightness. */
|
|
19
|
+
export declare const oklabToOkhslLightness: (x: number) => number;
|
|
20
|
+
/** Oklab [L, a, b] to linear sRGB [r, g, b] (0-1, may leave the gamut). */
|
|
21
|
+
export declare function oklabToLinearSrgb(lab: Vector): Vector;
|
|
22
|
+
/** sRGB channels (0-255) to Oklab [L, a, b]. */
|
|
23
|
+
export declare function rgbToOklab({ r, g, b }: RgbColor): Vector;
|
|
24
|
+
/** Linear sRGB [r, g, b] to sRGB channels (0-255, rounded), clipping out-of-gamut channels. */
|
|
25
|
+
export declare function linearSrgbToRgb(linear: Vector): RgbColor;
|
|
26
|
+
/**
|
|
27
|
+
* Convert OKHSL to sRGB channels (0-255, rounded), clipping out-of-gamut channels.
|
|
28
|
+
* @param hue Hue in degrees.
|
|
29
|
+
* @param saturation Saturation, 0-1.
|
|
30
|
+
* @param lightness Lightness, 0-1.
|
|
31
|
+
*/
|
|
32
|
+
export declare function okhslToRgb(hue: number, saturation: number, lightness: number): RgbColor;
|
|
33
|
+
/**
|
|
34
|
+
* Convert sRGB channels (0-255) to OKHSL.
|
|
35
|
+
* @returns Hue `h` in degrees (0 for grays), saturation `s` and lightness `l` 0-1.
|
|
36
|
+
*/
|
|
37
|
+
export declare function rgbToOkhsl(rgb: RgbColor): {
|
|
38
|
+
h: number;
|
|
39
|
+
s: number;
|
|
40
|
+
l: number;
|
|
41
|
+
};
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=oklab.d.ts.map
|
package/dist/oklab.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oklab and OKHSL <-> sRGB conversion. `colors.ts` builds its OKLCH, OKHSL, and color mixing on it.
|
|
3
|
+
*
|
|
4
|
+
* Oklab and OKHSL are Björn Ottosson's color spaces; OKHSL's saturation is relative to the sRGB gamut at
|
|
5
|
+
* each hue and lightness. This is a port of his reference implementation (https://bottosson.github.io/posts/colorpicker/),
|
|
6
|
+
* Copyright (c) 2021 Björn Ottosson, used under the MIT license:
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
9
|
+
* associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
10
|
+
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
12
|
+
* following conditions: The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
* copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
14
|
+
* KIND, EXPRESS OR IMPLIED.
|
|
15
|
+
*/
|
|
16
|
+
const multiply = (m, [x, y, z]) => m.map((row) => row[0] * x + row[1] * y + row[2] * z);
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// OKHSL <-> sRGB
|
|
19
|
+
// ============================================================================
|
|
20
|
+
const LINEAR_SRGB_TO_LMS = [
|
|
21
|
+
[0.4122214694707629, 0.5363325372617349, 0.0514459932675022],
|
|
22
|
+
[0.2119034958178251, 0.6806995506452344, 0.1073969535369405],
|
|
23
|
+
[0.0883024591900564, 0.2817188391361215, 0.6299787016738222],
|
|
24
|
+
];
|
|
25
|
+
const LMS_TO_LAB = [
|
|
26
|
+
[0.210454268309314, 0.793617774702305, -0.0040720430116193],
|
|
27
|
+
[1.9779985324311684, -2.42859224204858, 0.450593709617411],
|
|
28
|
+
[0.0259040424655478, 0.7827717124575296, -0.8086757549230774],
|
|
29
|
+
];
|
|
30
|
+
const LAB_TO_LMS = [
|
|
31
|
+
[1, 0.3963377773761749, 0.2158037573099136],
|
|
32
|
+
[1, -0.1055613458156586, -0.0638541728258133],
|
|
33
|
+
[1, -0.0894841775298119, -1.2914855480194092],
|
|
34
|
+
];
|
|
35
|
+
const LMS_TO_LINEAR_SRGB = [
|
|
36
|
+
[4.0767416360759583, -3.3077115392580629, 0.2309699031821043],
|
|
37
|
+
[-1.2684379732850315, 2.6097573492876882, -0.341319376002657],
|
|
38
|
+
[-0.0041960761386756, -0.7034186179359362, 1.7076146940746117],
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Per sRGB channel (red, green, blue): the (a, b) half-plane where that channel clips
|
|
42
|
+
* first, and the polynomial approximating the maximum saturation there.
|
|
43
|
+
*/
|
|
44
|
+
const SATURATION_FIT = [
|
|
45
|
+
[
|
|
46
|
+
[-1.8817031, -0.80936501],
|
|
47
|
+
[1.19086277, 1.76576728, 0.59662641, 0.75515197, 0.56771245],
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
[1.8144408, -1.19445267],
|
|
51
|
+
[0.73956515, -0.45954404, 0.08285427, 0.12541073, -0.14503204],
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
[0.13110758, 1.81333971],
|
|
55
|
+
[1.35733652, -0.00915799, -1.1513021, -0.50559606, 0.00692167],
|
|
56
|
+
],
|
|
57
|
+
];
|
|
58
|
+
const K1 = 0.206;
|
|
59
|
+
const K2 = 0.03;
|
|
60
|
+
const K3 = (1 + K1) / (1 + K2);
|
|
61
|
+
/** Oklab lightness to OKHSL lightness. */
|
|
62
|
+
export const oklabToOkhslLightness = (x) => 0.5 * (K3 * x - K1 + Math.sqrt((K3 * x - K1) ** 2 + 4 * K2 * K3 * x));
|
|
63
|
+
/** OKHSL lightness to Oklab lightness. */
|
|
64
|
+
const okhslToOklabLightness = (x) => (x * x + K1 * x) / (K3 * (x + K2));
|
|
65
|
+
/** sRGB transfer function: linear to encoded channel, both 0-1. */
|
|
66
|
+
const linearToSrgb = (value) => value > 0.0031308 ? 1.055 * value ** (1 / 2.4) - 0.055 : 12.92 * value;
|
|
67
|
+
/** Inverse sRGB transfer function: encoded to linear channel, both 0-1. */
|
|
68
|
+
const srgbToLinear = (value) => (value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4);
|
|
69
|
+
/** Oklab [L, a, b] to linear sRGB [r, g, b] (0-1, may leave the gamut). */
|
|
70
|
+
export function oklabToLinearSrgb(lab) {
|
|
71
|
+
return multiply(LMS_TO_LINEAR_SRGB, multiply(LAB_TO_LMS, lab).map((value) => value ** 3));
|
|
72
|
+
}
|
|
73
|
+
/** Linear sRGB [r, g, b] (0-1) to Oklab [L, a, b]. */
|
|
74
|
+
function linearSrgbToOklab(rgb) {
|
|
75
|
+
return multiply(LMS_TO_LAB, multiply(LINEAR_SRGB_TO_LMS, rgb).map(Math.cbrt));
|
|
76
|
+
}
|
|
77
|
+
/** sRGB channels (0-255) to Oklab [L, a, b]. */
|
|
78
|
+
export function rgbToOklab({ r, g, b }) {
|
|
79
|
+
return linearSrgbToOklab([r / 255, g / 255, b / 255].map(srgbToLinear));
|
|
80
|
+
}
|
|
81
|
+
/** Linear sRGB [r, g, b] to sRGB channels (0-255, rounded), clipping out-of-gamut channels. */
|
|
82
|
+
export function linearSrgbToRgb(linear) {
|
|
83
|
+
const [r, g, b] = linear.map((value) => Math.round(Math.min(1, Math.max(0, linearToSrgb(value))) * 255));
|
|
84
|
+
return { r, g, b };
|
|
85
|
+
}
|
|
86
|
+
/** Rate of change of each cube-root LMS component along a chroma direction (a, b). */
|
|
87
|
+
function lmsSlopes(a, b) {
|
|
88
|
+
return [LAB_TO_LMS[0], LAB_TO_LMS[1], LAB_TO_LMS[2]].map((row) => row[1] * a + row[2] * b);
|
|
89
|
+
}
|
|
90
|
+
/** Largest saturation (C/L) inside sRGB for hue (a, b): polynomial fit plus one Halley step. */
|
|
91
|
+
function maxSaturation(a, b) {
|
|
92
|
+
const channel = SATURATION_FIT.findIndex(([[x, y]], index) => index === 2 || x * a + y * b > 1);
|
|
93
|
+
const [k0, k1, k2, k3, k4] = SATURATION_FIT[channel][1];
|
|
94
|
+
const weights = LMS_TO_LINEAR_SRGB[channel];
|
|
95
|
+
const saturation = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;
|
|
96
|
+
const slopes = lmsSlopes(a, b);
|
|
97
|
+
const base = slopes.map((k) => 1 + saturation * k);
|
|
98
|
+
const dot = (values) => values.reduce((sum, value, index) => sum + weights[index] * value, 0);
|
|
99
|
+
const f = dot(base.map((value) => value ** 3));
|
|
100
|
+
const f1 = dot(base.map((value, index) => 3 * slopes[index] * value ** 2));
|
|
101
|
+
const f2 = dot(base.map((value, index) => 6 * slopes[index] ** 2 * value));
|
|
102
|
+
return saturation - (f * f1) / (f1 * f1 - 0.5 * f * f2);
|
|
103
|
+
}
|
|
104
|
+
/** Oklab lightness and chroma of the most saturated sRGB color of hue (a, b). */
|
|
105
|
+
function cusp(a, b) {
|
|
106
|
+
const saturation = maxSaturation(a, b);
|
|
107
|
+
const lightness = Math.cbrt(1 / Math.max(...oklabToLinearSrgb([1, saturation * a, saturation * b])));
|
|
108
|
+
return [lightness, lightness * saturation];
|
|
109
|
+
}
|
|
110
|
+
/** Chroma where the constant-lightness line at `lightness` leaves the sRGB gamut. */
|
|
111
|
+
function maxChroma(a, b, lightness, [cuspL, cuspC]) {
|
|
112
|
+
if (lightness <= cuspL)
|
|
113
|
+
return (cuspC * lightness) / cuspL;
|
|
114
|
+
// Upper half: triangle edge, then one Halley step against each channel reaching 1.
|
|
115
|
+
const t = (cuspC * (lightness - 1)) / (cuspL - 1);
|
|
116
|
+
const slopes = lmsSlopes(a, b);
|
|
117
|
+
const lms = slopes.map((k) => lightness + t * k);
|
|
118
|
+
const cubes = lms.map((value) => value ** 3);
|
|
119
|
+
const first = lms.map((value, index) => 3 * slopes[index] * value ** 2);
|
|
120
|
+
const second = lms.map((value, index) => 6 * slopes[index] ** 2 * value);
|
|
121
|
+
const dot = (row, values) => row[0] * values[0] + row[1] * values[1] + row[2] * values[2];
|
|
122
|
+
const steps = LMS_TO_LINEAR_SRGB.map((row) => {
|
|
123
|
+
const f = dot(row, cubes) - 1;
|
|
124
|
+
const f1 = dot(row, first);
|
|
125
|
+
const f2 = dot(row, second);
|
|
126
|
+
const u = f1 / (f1 * f1 - 0.5 * f * f2);
|
|
127
|
+
return u >= 0 ? -f * u : Number.MAX_VALUE;
|
|
128
|
+
});
|
|
129
|
+
return t + Math.min(...steps);
|
|
130
|
+
}
|
|
131
|
+
/** OKHSL's chroma reference points at lightness L and hue (a, b): [c0, cMid, cMax]. */
|
|
132
|
+
function chromaStops(L, a, b) {
|
|
133
|
+
const peak = cusp(a, b);
|
|
134
|
+
const cMax = maxChroma(a, b, L, peak);
|
|
135
|
+
const k = cMax / Math.min(L * (peak[1] / peak[0]), (1 - L) * (peak[1] / (1 - peak[0])));
|
|
136
|
+
const midS = 0.11516993 +
|
|
137
|
+
1 /
|
|
138
|
+
(7.4477897 +
|
|
139
|
+
4.1590124 * b +
|
|
140
|
+
a *
|
|
141
|
+
(-2.19557347 +
|
|
142
|
+
1.75198401 * b +
|
|
143
|
+
a * (-2.13704948 - 10.02301043 * b + a * (-4.24894561 + 5.38770819 * b + 4.69891013 * a))));
|
|
144
|
+
const midT = 0.11239642 +
|
|
145
|
+
1 /
|
|
146
|
+
(1.6132032 -
|
|
147
|
+
0.68124379 * b +
|
|
148
|
+
a *
|
|
149
|
+
(0.40370612 +
|
|
150
|
+
0.90148123 * b +
|
|
151
|
+
a * (-0.27087943 + 0.6122399 * b + a * (0.00299215 - 0.45399568 * b - 0.14661872 * a))));
|
|
152
|
+
const cMid = 0.9 * k * Math.sqrt(Math.sqrt(1 / (1 / (L * midS) ** 4 + 1 / ((1 - L) * midT) ** 4)));
|
|
153
|
+
const c0 = Math.sqrt(1 / (1 / (L * 0.4) ** 2 + 1 / ((1 - L) * 0.8) ** 2));
|
|
154
|
+
return [c0, cMid, cMax];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Convert OKHSL to sRGB channels (0-255, rounded), clipping out-of-gamut channels.
|
|
158
|
+
* @param hue Hue in degrees.
|
|
159
|
+
* @param saturation Saturation, 0-1.
|
|
160
|
+
* @param lightness Lightness, 0-1.
|
|
161
|
+
*/
|
|
162
|
+
export function okhslToRgb(hue, saturation, lightness) {
|
|
163
|
+
const L = okhslToOklabLightness(lightness);
|
|
164
|
+
let lab = [L, 0, 0];
|
|
165
|
+
if (L > 0 && L < 1 && saturation > 0) {
|
|
166
|
+
const angle = (2 * Math.PI * (((hue % 360) + 360) % 360)) / 360;
|
|
167
|
+
const a = Math.cos(angle);
|
|
168
|
+
const b = Math.sin(angle);
|
|
169
|
+
const [c0, cMid, cMax] = chromaStops(L, a, b);
|
|
170
|
+
// Chroma rises from 0 through cMid at s = 0.8 to cMax at s = 1.
|
|
171
|
+
let chroma;
|
|
172
|
+
if (saturation < 0.8) {
|
|
173
|
+
const t = 1.25 * saturation;
|
|
174
|
+
const k1 = 0.8 * c0;
|
|
175
|
+
chroma = (t * k1) / (1 - (1 - k1 / cMid) * t);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const t = 5 * (saturation - 0.8);
|
|
179
|
+
const k1 = (0.2 * cMid ** 2 * 1.25 ** 2) / c0;
|
|
180
|
+
chroma = cMid + (t * k1) / (1 - (1 - k1 / (cMax - cMid)) * t);
|
|
181
|
+
}
|
|
182
|
+
lab = [L, chroma * a, chroma * b];
|
|
183
|
+
}
|
|
184
|
+
return linearSrgbToRgb(oklabToLinearSrgb(lab));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Convert sRGB channels (0-255) to OKHSL.
|
|
188
|
+
* @returns Hue `h` in degrees (0 for grays), saturation `s` and lightness `l` 0-1.
|
|
189
|
+
*/
|
|
190
|
+
export function rgbToOkhsl(rgb) {
|
|
191
|
+
const [L, labA, labB] = rgbToOklab(rgb);
|
|
192
|
+
const chroma = Math.hypot(labA, labB);
|
|
193
|
+
const lightness = oklabToOkhslLightness(L);
|
|
194
|
+
if (chroma < 1e-9 || lightness <= 0 || lightness >= 1)
|
|
195
|
+
return { h: 0, s: 0, l: lightness };
|
|
196
|
+
const hue = ((Math.atan2(labB, labA) * 180) / Math.PI + 360) % 360;
|
|
197
|
+
const [c0, cMid, cMax] = chromaStops(L, labA / chroma, labB / chroma);
|
|
198
|
+
let saturation;
|
|
199
|
+
if (chroma < cMid) {
|
|
200
|
+
const k1 = 0.8 * c0;
|
|
201
|
+
saturation = 0.8 * (chroma / (k1 + (1 - k1 / cMid) * chroma));
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const k1 = (0.2 * cMid ** 2 * 1.25 ** 2) / c0;
|
|
205
|
+
const offset = chroma - cMid;
|
|
206
|
+
saturation = 0.8 + 0.2 * (offset / (k1 + (1 - k1 / (cMax - cMid)) * offset));
|
|
207
|
+
}
|
|
208
|
+
return { h: hue, s: Math.min(1, Math.max(0, saturation)), l: lightness };
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=oklab.js.map
|
|
@@ -46,6 +46,7 @@ export function getSlashCommandSuggestions(commands, prefix) {
|
|
|
46
46
|
label: name,
|
|
47
47
|
description: fullDesc || undefined,
|
|
48
48
|
searchText: isSkill && !explicitSkillNamespace ? skillName : name,
|
|
49
|
+
awaitsArguments: hint !== undefined,
|
|
49
50
|
},
|
|
50
51
|
];
|
|
51
52
|
});
|
|
@@ -58,6 +59,7 @@ export function getSlashCommandSuggestions(commands, prefix) {
|
|
|
58
59
|
label: SKILL_COMMAND_PREFIX,
|
|
59
60
|
description: "Browse available skills",
|
|
60
61
|
searchText: SKILL_COMMAND_PREFIX,
|
|
62
|
+
awaitsArguments: false,
|
|
61
63
|
});
|
|
62
64
|
}
|
|
63
65
|
return fuzzyFilter(commandItems, prefix, (item) => item.searchText)
|
|
@@ -65,6 +67,7 @@ export function getSlashCommandSuggestions(commands, prefix) {
|
|
|
65
67
|
value: item.name,
|
|
66
68
|
label: item.label,
|
|
67
69
|
...(item.description && { description: item.description }),
|
|
70
|
+
...(item.awaitsArguments && { awaitsArguments: true }),
|
|
68
71
|
index,
|
|
69
72
|
}))
|
|
70
73
|
.sort((left, right) => compareSlashCommandSuggestion(normalizedPrefix, left, right))
|
|
@@ -9,7 +9,7 @@ export function detectTerminalCapabilities(env = process.env, platform = process
|
|
|
9
9
|
const termProgram = (env.TERM_PROGRAM ?? "").toLowerCase();
|
|
10
10
|
const terminalEmulator = env.TERMINAL_EMULATOR ?? "";
|
|
11
11
|
const colorTerm = env.COLORTERM ?? "";
|
|
12
|
-
const trueColor = colorTerm === "truecolor" || colorTerm === "24bit";
|
|
12
|
+
const trueColor = colorTerm === "truecolor" || colorTerm === "24bit" || term.toLowerCase().endsWith("-direct");
|
|
13
13
|
if (env.TMUX || term.startsWith("tmux")) {
|
|
14
14
|
const state = probeTmuxImageState(env, execTmux);
|
|
15
15
|
const override = parseTmuxKittyTerminalOverride(env.PI_TUI_TMUX_KITTY_TERMINAL);
|
|
@@ -4,7 +4,24 @@ export interface RgbColor {
|
|
|
4
4
|
b: number;
|
|
5
5
|
}
|
|
6
6
|
export type TerminalColorScheme = "dark" | "light";
|
|
7
|
-
|
|
8
|
-
export
|
|
7
|
+
/** Colors the terminal reports for its current theme. */
|
|
8
|
+
export interface TerminalColors {
|
|
9
|
+
/** Default foreground (OSC 10). */
|
|
10
|
+
foreground?: RgbColor;
|
|
11
|
+
/** Default background (OSC 11). */
|
|
12
|
+
background?: RgbColor;
|
|
13
|
+
/** ANSI colors 0-15 (OSC 4). Only set when the terminal reported all 16. */
|
|
14
|
+
palette?: RgbColor[];
|
|
15
|
+
}
|
|
16
|
+
/** What an OSC color reply reports: the default foreground (OSC 10), background (OSC 11), or a palette index (OSC 4). */
|
|
17
|
+
export type OscColorTarget = "foreground" | "background" | number;
|
|
18
|
+
/**
|
|
19
|
+
* Parse an OSC 10, 11, or 4 color reply. Returns undefined when `data` is not such a reply;
|
|
20
|
+
* `rgb` is undefined when it is a reply with an unparseable color.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseOscColorResponse(data: string): {
|
|
23
|
+
target: OscColorTarget;
|
|
24
|
+
rgb: RgbColor | undefined;
|
|
25
|
+
} | undefined;
|
|
9
26
|
export declare function parseTerminalColorSchemeReport(data: string): TerminalColorScheme | undefined;
|
|
10
27
|
//# sourceMappingURL=terminal-colors.d.ts.map
|
package/dist/terminal-colors.js
CHANGED
|
@@ -15,17 +15,22 @@ function parseOscHexChannel(channel) {
|
|
|
15
15
|
}
|
|
16
16
|
return Math.round((parseInt(channel, 16) / max) * 255);
|
|
17
17
|
}
|
|
18
|
-
const
|
|
18
|
+
const OSC_COLOR_RESPONSE_PATTERN = /^\x1b\](?:(1[01])|4;(\d{1,3}));([^\x07\x1b]*)(?:\x07|\x1b\\)$/i;
|
|
19
19
|
const COLOR_SCHEME_REPORT_PATTERN = /^(?:\x1b\[\?997;(1|2)n)+$/;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Parse an OSC 10, 11, or 4 color reply. Returns undefined when `data` is not such a reply;
|
|
22
|
+
* `rgb` is undefined when it is a reply with an unparseable color.
|
|
23
|
+
*/
|
|
24
|
+
export function parseOscColorResponse(data) {
|
|
25
|
+
const match = data.match(OSC_COLOR_RESPONSE_PATTERN);
|
|
25
26
|
if (!match) {
|
|
26
27
|
return undefined;
|
|
27
28
|
}
|
|
28
|
-
const
|
|
29
|
+
const target = match[1] === "10" ? "foreground" : match[1] === "11" ? "background" : Number.parseInt(match[2], 10);
|
|
30
|
+
return { target, rgb: parseOscColorValue(match[3]) };
|
|
31
|
+
}
|
|
32
|
+
function parseOscColorValue(rawValue) {
|
|
33
|
+
const value = rawValue.trim();
|
|
29
34
|
if (value.startsWith("#")) {
|
|
30
35
|
const hex = value.slice(1);
|
|
31
36
|
if (/^[0-9a-f]{6}$/i.test(hex)) {
|
package/dist/terminal-image.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TerminalColorMode } from "./colors.ts";
|
|
1
2
|
import { type DetectedTerminalCapabilities, type TerminalCapabilities } from "./terminal-capabilities.ts";
|
|
2
3
|
export type { ImageProtocol, TerminalCapabilities } from "./terminal-capabilities.ts";
|
|
3
4
|
export { outerKittyGraphicsMode } from "./terminal-capabilities.ts";
|
|
@@ -28,6 +29,7 @@ export declare function getCellDimensions(): CellDimensions;
|
|
|
28
29
|
export declare function setCellDimensions(dims: CellDimensions): void;
|
|
29
30
|
export declare function detectCapabilities(tmuxForwardsHyperlink?: () => boolean, tmuxPassthroughState?: () => TmuxPassthroughState): DetectedTerminalCapabilities;
|
|
30
31
|
export declare function getCapabilities(): TerminalCapabilities;
|
|
32
|
+
export declare function getTerminalColorMode(capabilities?: TerminalCapabilities): TerminalColorMode;
|
|
31
33
|
export declare function resetCapabilitiesCache(): void;
|
|
32
34
|
/** Override selected auto-detected capabilities. */
|
|
33
35
|
export declare function setCapabilityOverrides(overrides: Partial<TerminalCapabilities>): void;
|
|
@@ -109,7 +111,7 @@ export declare function getKittyImageMetadata(line: string): KittyImageMetadata
|
|
|
109
111
|
/** Build a placement-only command for an image line emitted by {@link renderImage}. */
|
|
110
112
|
export declare function getKittyImagePlacement(line: string): KittyImagePlacement | undefined;
|
|
111
113
|
export declare function cropKittyImageLine(line: string, hiddenRows: number, visibleRows: number): string;
|
|
112
|
-
export declare function calculateImageCellSize(imageDimensions: ImageDimensions, maxWidthCells: number, maxHeightCells?: number, cellDimensions?: CellDimensions): ImageCellSize;
|
|
114
|
+
export declare function calculateImageCellSize(imageDimensions: ImageDimensions, maxWidthCells: number, maxHeightCells?: number, cellDimensions?: CellDimensions, optimizeAspectRatio?: boolean): ImageCellSize;
|
|
113
115
|
export declare function getPngDimensions(base64Data: string): ImageDimensions | null;
|
|
114
116
|
export declare function getJpegDimensions(base64Data: string): ImageDimensions | null;
|
|
115
117
|
export declare function getGifDimensions(base64Data: string): ImageDimensions | null;
|
package/dist/terminal-image.js
CHANGED
|
@@ -35,7 +35,7 @@ function detectCapabilitiesFromEnvironment(tmuxForwardsHyperlink) {
|
|
|
35
35
|
const terminalEmulator = process.env.TERMINAL_EMULATOR?.toLowerCase() || "";
|
|
36
36
|
const term = process.env.TERM?.toLowerCase() || "";
|
|
37
37
|
const colorTerm = process.env.COLORTERM?.toLowerCase() || "";
|
|
38
|
-
const hasTrueColorHint = colorTerm === "truecolor" || colorTerm === "24bit";
|
|
38
|
+
const hasTrueColorHint = colorTerm === "truecolor" || colorTerm === "24bit" || term.endsWith("-direct");
|
|
39
39
|
const isWindowsConsole = process.platform === "win32";
|
|
40
40
|
// Emit OSC 8 hyperlinks only when tmux confirms it forwards.
|
|
41
41
|
// Image protocols are unreliable under tmux, so leave `images: null`.
|
|
@@ -132,6 +132,9 @@ export function getCapabilities() {
|
|
|
132
132
|
}
|
|
133
133
|
return cachedCapabilities;
|
|
134
134
|
}
|
|
135
|
+
export function getTerminalColorMode(capabilities = getCapabilities()) {
|
|
136
|
+
return capabilities.trueColor ? "truecolor" : "256color";
|
|
137
|
+
}
|
|
135
138
|
export function resetCapabilitiesCache() {
|
|
136
139
|
cachedCapabilities = null;
|
|
137
140
|
}
|
|
@@ -415,7 +418,15 @@ export function cropKittyImageLine(line, hiddenRows, visibleRows) {
|
|
|
415
418
|
controls.push(`y=${sourceY}`, `h=${sourceHeight}`, `r=${croppedRows}`);
|
|
416
419
|
return `${line.slice(0, match.index)}\x1b_G${controls.join(",")};${line.slice(match.index + match[0].length)}`;
|
|
417
420
|
}
|
|
418
|
-
|
|
421
|
+
function chooseLessDistortedCellCount(upperCount, idealCount) {
|
|
422
|
+
if (upperCount <= 1)
|
|
423
|
+
return upperCount;
|
|
424
|
+
const lowerCount = upperCount - 1;
|
|
425
|
+
const upperDistortion = Math.max(upperCount / idealCount, idealCount / upperCount);
|
|
426
|
+
const lowerDistortion = Math.max(lowerCount / idealCount, idealCount / lowerCount);
|
|
427
|
+
return lowerDistortion < upperDistortion ? lowerCount : upperCount;
|
|
428
|
+
}
|
|
429
|
+
export function calculateImageCellSize(imageDimensions, maxWidthCells, maxHeightCells, cellDimensions = { widthPx: 9, heightPx: 18 }, optimizeAspectRatio = false) {
|
|
419
430
|
const maxWidth = Math.max(1, Math.floor(maxWidthCells));
|
|
420
431
|
const maxHeight = maxHeightCells === undefined ? undefined : Math.max(1, Math.floor(maxHeightCells));
|
|
421
432
|
const imageWidth = Math.max(1, imageDimensions.widthPx);
|
|
@@ -425,12 +436,24 @@ export function calculateImageCellSize(imageDimensions, maxWidthCells, maxHeight
|
|
|
425
436
|
const scale = Math.min(widthScale, heightScale);
|
|
426
437
|
const scaledWidthPx = imageWidth * scale;
|
|
427
438
|
const scaledHeightPx = imageHeight * scale;
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
rows
|
|
433
|
-
}
|
|
439
|
+
let columns = Math.max(1, Math.min(maxWidth, Math.ceil(scaledWidthPx / cellDimensions.widthPx)));
|
|
440
|
+
const heightRows = scaledHeightPx / cellDimensions.heightPx;
|
|
441
|
+
let rows = Math.max(1, Math.ceil(heightRows));
|
|
442
|
+
if (maxHeight !== undefined) {
|
|
443
|
+
rows = Math.min(maxHeight, rows);
|
|
444
|
+
}
|
|
445
|
+
if (!optimizeAspectRatio) {
|
|
446
|
+
return { columns, rows };
|
|
447
|
+
}
|
|
448
|
+
if (widthScale <= heightScale) {
|
|
449
|
+
const idealRows = (columns * cellDimensions.widthPx * imageHeight) / (imageWidth * cellDimensions.heightPx);
|
|
450
|
+
rows = chooseLessDistortedCellCount(rows, idealRows);
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
const idealColumns = (rows * cellDimensions.heightPx * imageWidth) / (imageHeight * cellDimensions.widthPx);
|
|
454
|
+
columns = chooseLessDistortedCellCount(columns, idealColumns);
|
|
455
|
+
}
|
|
456
|
+
return { columns, rows };
|
|
434
457
|
}
|
|
435
458
|
export function getPngDimensions(base64Data) {
|
|
436
459
|
try {
|
|
@@ -564,7 +587,8 @@ export function renderImage(base64Data, imageDimensions, options = {}) {
|
|
|
564
587
|
return null;
|
|
565
588
|
}
|
|
566
589
|
const maxWidth = options.maxWidthCells ?? 80;
|
|
567
|
-
|
|
590
|
+
// Reduce Kitty's cell-aligned distortion without shrinking iTerm2 reservations.
|
|
591
|
+
const size = calculateImageCellSize(imageDimensions, maxWidth, options.maxHeightCells, getCellDimensions(), caps.images === "kitty");
|
|
568
592
|
if (caps.images === "kitty" && caps.kittyUnicodePlaceholders) {
|
|
569
593
|
// Virtual placement: transmit once, then show the image with plain-text
|
|
570
594
|
// placeholder cells. Under tmux the placeholder cells move/clip with the
|
package/dist/terminal.d.ts
CHANGED
|
@@ -97,6 +97,8 @@ export declare class ProcessTerminal implements Terminal {
|
|
|
97
97
|
private _kittyProtocolActive;
|
|
98
98
|
private _modifyOtherKeysActive;
|
|
99
99
|
private keyboardProtocolPushed;
|
|
100
|
+
/** DA1 replies owed to keyboard protocol queries. Later DA1 replies answer other queries and are forwarded. */
|
|
101
|
+
private pendingKeyboardProtocolDeviceAttributes;
|
|
100
102
|
private keyboardProtocolNegotiationBuffer;
|
|
101
103
|
private discardingPrivateResponse;
|
|
102
104
|
private keyboardProtocolBufferFlushTimer?;
|
|
@@ -143,6 +145,7 @@ export declare class ProcessTerminal implements Terminal {
|
|
|
143
145
|
*/
|
|
144
146
|
private queryAndEnableKittyProtocol;
|
|
145
147
|
private handleKeyboardProtocolNegotiationSequence;
|
|
148
|
+
/** Returns the parsed negotiation reply with its full (possibly reassembled) sequence. */
|
|
146
149
|
private readKeyboardProtocolNegotiationSequence;
|
|
147
150
|
private setKeyboardProtocolNegotiationBuffer;
|
|
148
151
|
private clearKeyboardProtocolNegotiationBuffer;
|