Address terminal encoding review feedback

This commit is contained in:
shixu
2026-06-29 10:30:42 +08:00
parent b7491fd05a
commit 2e9c021e68
7 changed files with 38 additions and 7 deletions
@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import {
createStoredStringSyncHandlers,
readOptionalStoredStringValue,
readStoredStringValue,
resolveStoredStringUpdate,
} from "./useStoredString.ts";
@@ -85,12 +86,15 @@ test("stored string helpers read fallback and resolve updater-style toggles", (t
const storageKey = "netcatty:test-mode";
assert.equal(readStoredStringValue(storageKey, "edit", isTestMode), "edit");
assert.equal(readOptionalStoredStringValue(storageKey, isTestMode), null);
env.storage.setItem(storageKey, "preview");
assert.equal(readStoredStringValue(storageKey, "edit", isTestMode), "preview");
assert.equal(readOptionalStoredStringValue(storageKey, isTestMode), "preview");
env.storage.setItem(storageKey, "invalid");
assert.equal(readStoredStringValue(storageKey, "edit", isTestMode), "edit");
assert.equal(readOptionalStoredStringValue(storageKey, isTestMode), null);
assert.equal(resolveStoredStringUpdate<TestMode>("edit", "preview"), "preview");
assert.equal(
+9
View File
@@ -18,6 +18,15 @@ export const readStoredStringValue = <T extends string>(
return isAllowedValue(stored) ? stored : fallback;
};
export const readOptionalStoredStringValue = <T extends string>(
storageKey: string,
isAllowedValue: (value: string | null) => value is T,
): T | null => {
if (!canUseLocalStorage()) return null;
const stored = localStorageAdapter.readString(storageKey);
return isAllowedValue(stored) ? stored : null;
};
export const resolveStoredStringUpdate = <T extends string>(
currentValue: T,
nextValue: T | ((currentValue: T) => T),
+2 -3
View File
@@ -44,7 +44,7 @@ import { supportsZmodemTerminalDragDrop } from "../lib/zmodemDragDrop";
import { resolveHostAuth } from "../domain/sshAuth";
import { useTerminalBackend } from "../application/state/useTerminalBackend";
import { useStoredBoolean } from "../application/state/useStoredBoolean";
import { readStoredStringValue, useStoredString } from "../application/state/useStoredString";
import { readOptionalStoredStringValue, useStoredString } from "../application/state/useStoredString";
import { useSessionLogBackend } from "../application/state/useSessionLogBackend";
import { useTerminalLayoutSuppressActive } from "../application/state/terminalLayoutSuppressStore";
// SFTPModal removed - SFTP is now handled by SftpSidePanel in TerminalLayer
@@ -597,9 +597,8 @@ const TerminalComponent: React.FC<TerminalProps> = ({
const [terminalEncoding, setTerminalEncoding] = useState<TerminalEncodingPreference>(() => {
return resolveInitialTerminalEncoding(
host?.charset,
readStoredStringValue(
readOptionalStoredStringValue(
terminalEncodingStorageKey,
'utf-8',
isTerminalEncodingPreference,
),
);
@@ -7,11 +7,18 @@
*/
import type { Snippet } from "../../../domain/models";
import type { CompletionSuggestion } from "./completionEngine";
import { snippetAppliesToHost } from "../../../domain/snippetTargets";
const SNIPPET_BASE_SCORE = 2000; // Above history (1000+freq) per "snippet > history".
const SNIPPET_PREFIX_BONUS = 100;
function snippetAvailableForAutocomplete(snippet: Snippet, hostId?: string): boolean {
if (snippet.targetsAllHosts) return true;
if (snippet.targets && snippet.targets.length > 0) {
return hostId !== undefined && snippet.targets.includes(hostId);
}
return true;
}
export function getSnippetSuggestions(
input: string,
snippets: Snippet[],
@@ -22,7 +29,7 @@ export function getSnippetSuggestions(
const out: CompletionSuggestion[] = [];
for (const snippet of snippets) {
if (!snippetAppliesToHost(snippet, options.hostId)) continue;
if (!snippetAvailableForAutocomplete(snippet, options.hostId)) continue;
const label = (snippet.label || "").toLowerCase();
const firstLine = (snippet.command || "").split("\n")[0].trim().toLowerCase();
+1 -1
View File
@@ -1343,7 +1343,7 @@ export function useTerminalEffects(ctx: TerminalEffectsContext) {
const handleWindowFocus = () => {
if (!shouldRecoverOnAppResume()) return;
const term = termRef.current;
let term = termRef.current;
if (term) {
cancelScheduledUnfocusedRepaint(term);
forceTerminalRepaintBypassingAnimationFrame(term);
@@ -8,7 +8,7 @@ import { getTerminalSidePanelShellWidth } from './TerminalLayerSidePanelSection'
type TerminalLayerEffectsContext = Record<string, any>;
export function useTerminalLayerEffects(ctx: TerminalLayerEffectsContext) {
const { activeSidePanelTab, activeTabId, activeTabIdRef, activeWorkspace, activityTrackedSessions, cancelAnimationFrame, ChunkedEscapeFilter, clearTimeout, document, dropHint, filterTabsMap, focusedSessionId, getSessionActivityIdsToClear, handleToggleAiFromTopBar, handleToggleScriptsSidePanel, handleToggleSidePanel, hasNotifiableTerminalOutput, isComposeBarOpen, isFocusMode, isTerminalLayerVisible, lastSidePanelTabRef, Map, onSessionData, onSplitSessionRef, onToggleBroadcastRef, onToggleWorkspaceViewModeRef, prevFocusedSessionIdRef, refocusActiveTerminalSession, requestAnimationFrame, ResizeObserver, sessionActivityStore, sessions, Set, setAiMountedTabIds, setDropHint, setNotesMountedTabIds, setScriptsMountedTabIds, setSystemMountedTabIds, setSftpHostForTab, setSftpInitialLocationForTab, setSftpPendingUploadsForTab, setSidePanelOpenTabs, setThemeMountedTabIds, setTimeout, setupMcpApprovalBridge, setWorkspaceArea, sidePanelPosition, sidePanelWidth, sftpActiveHost, sftpHostForTab, shouldMarkSessionActivity, sidePanelOpenTabs, splitHorizontalHandlersRef, splitVerticalHandlersRef, terminalRendererCwdBySessionRef, toggleScriptsSidePanelRef, toggleSidePanelRef, validAIScopeTargetIds, validSessionActivityIds, window, workspaceBroadcastHandlersRef, workspaceFocusHandlersRef, workspaceInnerRef, workspaces } = ctx;
const { activeSidePanelTab, activeTabId, activeTabIdRef, activeWorkspace, activityTrackedSessions, cancelAnimationFrame, ChunkedEscapeFilter, clearTimeout, clearTopTabsPreviewVars, document, dropHint, filterTabsMap, focusedSessionId, getSessionActivityIdsToClear, handleToggleAiFromTopBar, handleToggleScriptsSidePanel, handleToggleSidePanel, hasNotifiableTerminalOutput, isComposeBarOpen, isFocusMode, isTerminalLayerVisible, lastSidePanelTabRef, Map, onSessionData, onSplitSessionRef, onToggleBroadcastRef, onToggleWorkspaceViewModeRef, prevFocusedSessionIdRef, refocusActiveTerminalSession, requestAnimationFrame, ResizeObserver, sessionActivityStore, sessions, Set, setAiMountedTabIds, setDropHint, setNotesMountedTabIds, setScriptsMountedTabIds, setSystemMountedTabIds, setSftpHostForTab, setSftpInitialLocationForTab, setSftpPendingUploadsForTab, setSidePanelOpenTabs, setThemeMountedTabIds, setTimeout, setupMcpApprovalBridge, setWorkspaceArea, sidePanelPosition, sidePanelWidth, sftpActiveHost, sftpHostForTab, shouldMarkSessionActivity, sidePanelOpenTabs, splitHorizontalHandlersRef, splitVerticalHandlersRef, terminalRendererCwdBySessionRef, toggleScriptsSidePanelRef, toggleSidePanelRef, validAIScopeTargetIds, validSessionActivityIds, window, workspaceBroadcastHandlersRef, workspaceFocusHandlersRef, workspaceInnerRef, workspaces } = ctx;
const activeWorkspaceId = activeWorkspace?.id;
const activeWorkspaceViewMode = activeWorkspace?.viewMode;
@@ -60,6 +60,14 @@ export function useTerminalLayerEffects(ctx: TerminalLayerEffectsContext) {
});
}, [remeasureWorkspaceArea, requestAnimationFrame]);
useEffect(() => {
if (!isTerminalLayerVisible) {
if (typeof clearTopTabsPreviewVars === 'function') {
clearTopTabsPreviewVars();
}
}
}, [clearTopTabsPreviewVars, isTerminalLayerVisible]);
useEffect(() => {
const liveSessionIds = new Set(sessions.map((session) => session.id));
for (const sessionId of terminalRendererCwdBySessionRef.current.keys()) {
@@ -24,6 +24,10 @@ test("per-host remembered terminal encoding wins for supported or empty host cha
assert.equal(resolveInitialTerminalEncoding(undefined, "gb18030"), "gb18030");
});
test("missing per-host remembered encoding preserves configured GB18030 charset", () => {
assert.equal(resolveInitialTerminalEncoding("GB18030", null), "gb18030");
});
test("per-host remembered UTF-8 can override a supported GB18030 host charset", () => {
assert.equal(resolveInitialTerminalEncoding("GB18030", "utf-8"), "utf-8");
});