feat: configurable paste collapse thresholds (TUI + CLI)
Adds two new config keys: - paste_collapse_threshold (default: 5) — line count threshold for bracketed paste collapse in both TUI and CLI - paste_collapse_threshold_fallback (default: 0, disabled) — same for the fallback heuristic in terminals without bracketed paste support TUI frontend reads these from config.get full via applyDisplay/patchUiState. CLI reads from self.config at paste-handling time. Closes #5626 Related: #5623
This commit is contained in:
parent
973bb124a4
commit
ab42658dfc
6
cli.py
6
cli.py
@ -13351,7 +13351,8 @@ class HermesCLI:
|
|||||||
pasted_text = _sanitize_surrogates(pasted_text)
|
pasted_text = _sanitize_surrogates(pasted_text)
|
||||||
line_count = pasted_text.count('\n')
|
line_count = pasted_text.count('\n')
|
||||||
buf = event.current_buffer
|
buf = event.current_buffer
|
||||||
if line_count >= 5 and not buf.text.strip().startswith('/'):
|
threshold = self.config.get("paste_collapse_threshold", 5)
|
||||||
|
if threshold > 0 and line_count >= threshold and not buf.text.strip().startswith('/'):
|
||||||
_paste_counter[0] += 1
|
_paste_counter[0] += 1
|
||||||
paste_dir = _hermes_home / "pastes"
|
paste_dir = _hermes_home / "pastes"
|
||||||
paste_dir.mkdir(parents=True, exist_ok=True)
|
paste_dir.mkdir(parents=True, exist_ok=True)
|
||||||
@ -13520,7 +13521,8 @@ class HermesCLI:
|
|||||||
newlines_added = line_count - _prev_newline_count[0]
|
newlines_added = line_count - _prev_newline_count[0]
|
||||||
_prev_newline_count[0] = line_count
|
_prev_newline_count[0] = line_count
|
||||||
is_paste = chars_added > 1 or newlines_added >= 4
|
is_paste = chars_added > 1 or newlines_added >= 4
|
||||||
if line_count >= 5 and is_paste and not text.startswith('/'):
|
threshold = self.config.get("paste_collapse_threshold_fallback", 0)
|
||||||
|
if threshold > 0 and line_count >= threshold and is_paste and not text.startswith('/'):
|
||||||
_paste_counter[0] += 1
|
_paste_counter[0] += 1
|
||||||
paste_dir = _hermes_home / "pastes"
|
paste_dir = _hermes_home / "pastes"
|
||||||
paste_dir.mkdir(parents=True, exist_ok=True)
|
paste_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
@ -1771,6 +1771,7 @@ DEFAULT_CONFIG = {
|
|||||||
"servers": {},
|
"servers": {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
# X (Twitter) Search via xAI's built-in x_search Responses tool.
|
# X (Twitter) Search via xAI's built-in x_search Responses tool.
|
||||||
# The tool registers when xAI credentials are available (SuperGrok
|
# The tool registers when xAI credentials are available (SuperGrok
|
||||||
# OAuth or XAI_API_KEY) AND the x_search toolset is enabled in
|
# OAuth or XAI_API_KEY) AND the x_search toolset is enabled in
|
||||||
@ -1827,8 +1828,18 @@ DEFAULT_CONFIG = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
# Paste collapse thresholds (TUI + CLI).
|
||||||
|
# collapse_threshold: paste collapses to a file reference when line count
|
||||||
|
# exceeds this value (bracketed paste, safe: appends to existing text).
|
||||||
|
# collapse_threshold_fallback: same but for the fallback heuristic used
|
||||||
|
# by terminals without bracketed paste support (destructive: replaces
|
||||||
|
# entire buffer). 0 = disabled.
|
||||||
|
"paste_collapse_threshold": 5,
|
||||||
|
"paste_collapse_threshold_fallback": 0,
|
||||||
|
|
||||||
|
|
||||||
# Config schema version - bump this when adding new required fields
|
# Config schema version - bump this when adding new required fields
|
||||||
"_config_version": 23,
|
"_config_version": 24,
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@ -105,6 +105,8 @@ export interface UiState {
|
|||||||
info: null | SessionInfo
|
info: null | SessionInfo
|
||||||
inlineDiffs: boolean
|
inlineDiffs: boolean
|
||||||
mouseTracking: MouseTrackingMode
|
mouseTracking: MouseTrackingMode
|
||||||
|
pasteCollapseLines: number
|
||||||
|
|
||||||
sections: SectionVisibility
|
sections: SectionVisibility
|
||||||
showCost: boolean
|
showCost: boolean
|
||||||
showReasoning: boolean
|
showReasoning: boolean
|
||||||
|
|||||||
@ -17,6 +17,7 @@ const buildUiState = (): UiState => ({
|
|||||||
info: null,
|
info: null,
|
||||||
inlineDiffs: true,
|
inlineDiffs: true,
|
||||||
mouseTracking: MOUSE_TRACKING,
|
mouseTracking: MOUSE_TRACKING,
|
||||||
|
pasteCollapseLines: 5,
|
||||||
sections: {},
|
sections: {},
|
||||||
showCost: false,
|
showCost: false,
|
||||||
showReasoning: false,
|
showReasoning: false,
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { useStore } from '@nanostores/react'
|
|||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import type { PasteEvent } from '../components/textInput.js'
|
import type { PasteEvent } from '../components/textInput.js'
|
||||||
import { LARGE_PASTE } from '../config/limits.js'
|
|
||||||
import type { ImageAttachResponse, InputDetectDropResponse } from '../gatewayTypes.js'
|
import type { ImageAttachResponse, InputDetectDropResponse } from '../gatewayTypes.js'
|
||||||
import { useCompletion } from '../hooks/useCompletion.js'
|
import { useCompletion } from '../hooks/useCompletion.js'
|
||||||
import { useInputHistory } from '../hooks/useInputHistory.js'
|
import { useInputHistory } from '../hooks/useInputHistory.js'
|
||||||
@ -190,8 +189,9 @@ export function useComposerState({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lineCount = cleanedText.split('\n').length
|
const lineCount = cleanedText.split('\n').length
|
||||||
|
const pasteCollapseLines = getUiState().pasteCollapseLines
|
||||||
|
|
||||||
if (cleanedText.length < LARGE_PASTE.chars && lineCount < LARGE_PASTE.lines) {
|
if (pasteCollapseLines === 0 || lineCount < pasteCollapseLines) {
|
||||||
return {
|
return {
|
||||||
cursor: cursor + cleanedText.length,
|
cursor: cursor + cleanedText.length,
|
||||||
value: value.slice(0, cursor) + cleanedText + value.slice(cursor)
|
value: value.slice(0, cursor) + cleanedText + value.slice(cursor)
|
||||||
|
|||||||
@ -142,6 +142,17 @@ const _voiceRecordKeyFromConfig = (cfg: ConfigFullResponse | null): ParsedVoiceR
|
|||||||
return raw ? parseVoiceRecordKey(raw) : DEFAULT_VOICE_RECORD_KEY
|
return raw ? parseVoiceRecordKey(raw) : DEFAULT_VOICE_RECORD_KEY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _pasteCollapseLinesFromConfig = (cfg: ConfigFullResponse | null): number => {
|
||||||
|
if (!cfg?.config) return 5
|
||||||
|
const raw = cfg.config.paste_collapse_threshold
|
||||||
|
if (typeof raw === 'number' && Number.isFinite(raw) && raw >= 0) return Math.round(raw)
|
||||||
|
if (typeof raw === 'string') {
|
||||||
|
const n = parseInt(raw, 10)
|
||||||
|
if (Number.isFinite(n) && n >= 0) return n
|
||||||
|
}
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
|
||||||
/** Fetch ``config.get full`` and fan the result through ``applyDisplay``.
|
/** Fetch ``config.get full`` and fan the result through ``applyDisplay``.
|
||||||
*
|
*
|
||||||
* Extracted so the mtime-reload path can be exercised by the test
|
* Extracted so the mtime-reload path can be exercised by the test
|
||||||
@ -188,6 +199,7 @@ export const applyDisplay = (
|
|||||||
indicatorStyle: normalizeIndicatorStyle(d.tui_status_indicator),
|
indicatorStyle: normalizeIndicatorStyle(d.tui_status_indicator),
|
||||||
inlineDiffs: d.inline_diffs !== false,
|
inlineDiffs: d.inline_diffs !== false,
|
||||||
mouseTracking: normalizeMouseTracking(d),
|
mouseTracking: normalizeMouseTracking(d),
|
||||||
|
pasteCollapseLines: _pasteCollapseLinesFromConfig(cfg),
|
||||||
sections: resolveSections(d.sections),
|
sections: resolveSections(d.sections),
|
||||||
showCost: !!d.show_cost,
|
showCost: !!d.show_cost,
|
||||||
showReasoning: !!d.show_reasoning,
|
showReasoning: !!d.show_reasoning,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export const LARGE_PASTE = { chars: 8000, lines: 80 }
|
export const LARGE_PASTE = { lines: 5 }
|
||||||
|
|
||||||
export const LIVE_RENDER_MAX_CHARS = 16_000
|
export const LIVE_RENDER_MAX_CHARS = 16_000
|
||||||
export const LIVE_RENDER_MAX_LINES = 240
|
export const LIVE_RENDER_MAX_LINES = 240
|
||||||
|
|||||||
@ -82,7 +82,7 @@ export interface ConfigVoiceConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigFullResponse {
|
export interface ConfigFullResponse {
|
||||||
config?: { display?: ConfigDisplayConfig; voice?: ConfigVoiceConfig }
|
config?: { display?: ConfigDisplayConfig; voice?: ConfigVoiceConfig; paste_collapse_threshold?: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigMtimeResponse {
|
export interface ConfigMtimeResponse {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user