mirror of
https://github.com/tiennm99/ccstatusline.git
synced 2026-10-13 10:15:39 +00:00
1 parent
76d9af84a5
commit
d025c707e6
9 files changed
+69
-11
No files matched your search
@@ -15,6 +15,7 @@ import {
|
||||
getAvailableBackgroundColorsForUI,
|
||||
getAvailableColorsForUI
|
||||
} from '../../utils/colors';
|
||||
import { shouldInsertInput } from '../../utils/input-guards';
|
||||
import { getWidget } from '../../utils/widgets';
|
||||
|
||||
import { ConfirmDialog } from './ConfirmDialog';
|
||||
@@ -97,7 +98,7 @@ export const ColorMenu: React.FC<ColorMenuProps> = ({ widgets, lineIndex, settin
|
||||
}
|
||||
} else if (key.backspace || key.delete) {
|
||||
setHexInput(hexInput.slice(0, -1));
|
||||
} else if (input && hexInput.length < 6) {
|
||||
} else if (shouldInsertInput(input, key) && hexInput.length < 6) {
|
||||
// Only accept hex characters (0-9, A-F, a-f)
|
||||
const upperInput = input.toUpperCase();
|
||||
if (/^[0-9A-F]$/.test(upperInput)) {
|
||||
@@ -144,7 +145,7 @@ export const ColorMenu: React.FC<ColorMenuProps> = ({ widgets, lineIndex, settin
|
||||
}
|
||||
} else if (key.backspace || key.delete) {
|
||||
setAnsi256Input(ansi256Input.slice(0, -1));
|
||||
} else if (input && ansi256Input.length < 3) {
|
||||
} else if (shouldInsertInput(input, key) && ansi256Input.length < 3) {
|
||||
// Only accept numeric characters (0-9)
|
||||
if (/^[0-9]$/.test(input)) {
|
||||
const newInput = ansi256Input + input;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getChalkColor,
|
||||
getColorDisplayName
|
||||
} from '../../utils/colors';
|
||||
import { shouldInsertInput } from '../../utils/input-guards';
|
||||
|
||||
import { ConfirmDialog } from './ConfirmDialog';
|
||||
|
||||
@@ -57,7 +58,7 @@ export const GlobalOverridesMenu: React.FC<GlobalOverridesMenuProps> = ({ settin
|
||||
setPaddingInput(paddingInput.slice(0, -1));
|
||||
} else if (key.delete) {
|
||||
// For simple text inputs without cursor, forward delete does nothing
|
||||
} else if (input) {
|
||||
} else if (shouldInsertInput(input, key)) {
|
||||
setPaddingInput(paddingInput + input);
|
||||
}
|
||||
} else if (editingSeparator) {
|
||||
@@ -86,7 +87,7 @@ export const GlobalOverridesMenu: React.FC<GlobalOverridesMenuProps> = ({ settin
|
||||
setSeparatorInput(separatorInput.slice(0, -1));
|
||||
} else if (key.delete) {
|
||||
// For simple text inputs without cursor, forward delete does nothing
|
||||
} else if (input) {
|
||||
} else if (shouldInsertInput(input, key)) {
|
||||
setSeparatorInput(separatorInput + input);
|
||||
}
|
||||
} else if (confirmingSeparator) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import type { Settings } from '../../types/Settings';
|
||||
import { shouldInsertInput } from '../../utils/input-guards';
|
||||
|
||||
export type EditorMode = 'separator' | 'startCap' | 'endCap';
|
||||
|
||||
@@ -133,7 +134,7 @@ export const PowerlineSeparatorEditor: React.FC<PowerlineSeparatorEditorProps> =
|
||||
} else if (key.backspace && cursorPos > 0) {
|
||||
setHexInput(hexInput.slice(0, cursorPos - 1) + hexInput.slice(cursorPos));
|
||||
setCursorPos(cursorPos - 1);
|
||||
} else if (input && /[0-9a-fA-F]/.test(input) && hexInput.length < 4) {
|
||||
} else if (shouldInsertInput(input, key) && /[0-9a-fA-F]/.test(input) && hexInput.length < 4) {
|
||||
setHexInput(hexInput.slice(0, cursorPos) + input.toUpperCase() + hexInput.slice(cursorPos));
|
||||
setCursorPos(cursorPos + 1);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import React, { useState } from 'react';
|
||||
|
||||
import type { FlexMode } from '../../types/FlexMode';
|
||||
import type { Settings } from '../../types/Settings';
|
||||
import { shouldInsertInput } from '../../utils/input-guards';
|
||||
|
||||
export interface TerminalWidthMenuProps {
|
||||
settings: Settings;
|
||||
@@ -58,7 +59,7 @@ export const TerminalWidthMenu: React.FC<TerminalWidthMenuProps> = ({ settings,
|
||||
setValidationError(null);
|
||||
} else if (key.delete) {
|
||||
// For simple number inputs, forward delete does nothing since there's no cursor position
|
||||
} else if (input && /\d/.test(input)) {
|
||||
} else if (shouldInsertInput(input, key) && /\d/.test(input)) {
|
||||
const newValue = thresholdInput + input;
|
||||
if (newValue.length <= 2) {
|
||||
setThresholdInput(newValue);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
describe,
|
||||
expect,
|
||||
it
|
||||
} from 'vitest';
|
||||
|
||||
import { shouldInsertInput } from '../input-guards';
|
||||
|
||||
describe('shouldInsertInput', () => {
|
||||
it('allows regular printable input without modifiers', () => {
|
||||
expect(shouldInsertInput('s', {})).toBe(true);
|
||||
expect(shouldInsertInput('S', { shift: true })).toBe(true);
|
||||
});
|
||||
|
||||
it('blocks ctrl chords', () => {
|
||||
expect(shouldInsertInput('s', { ctrl: true })).toBe(false);
|
||||
});
|
||||
|
||||
it('blocks meta chords', () => {
|
||||
expect(shouldInsertInput('s', { meta: true })).toBe(false);
|
||||
});
|
||||
|
||||
it('blocks tab-based input', () => {
|
||||
expect(shouldInsertInput('\t', { tab: true })).toBe(false);
|
||||
});
|
||||
|
||||
it('blocks control characters and allows unicode text', () => {
|
||||
expect(shouldInsertInput('\u0013', {})).toBe(false);
|
||||
expect(shouldInsertInput('🙂', {})).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
export interface InputKeyLike {
|
||||
ctrl?: boolean;
|
||||
meta?: boolean;
|
||||
shift?: boolean;
|
||||
tab?: boolean;
|
||||
}
|
||||
|
||||
const CONTROL_CHAR_REGEX = /[\u0000-\u001F\u007F]/u;
|
||||
|
||||
export const shouldInsertInput = (input: string, key: InputKeyLike): boolean => {
|
||||
if (!input) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key.ctrl || key.meta || key.tab) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !CONTROL_CHAR_REGEX.test(input);
|
||||
};
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
WidgetEditorProps,
|
||||
WidgetItem
|
||||
} from '../types/Widget';
|
||||
import { shouldInsertInput } from '../utils/input-guards';
|
||||
|
||||
export class CurrentWorkingDirWidget implements Widget {
|
||||
getDefaultColor(): string { return 'blue'; }
|
||||
@@ -200,7 +201,7 @@ const CurrentWorkingDirEditor: React.FC<WidgetEditorProps> = ({ widget, onComple
|
||||
onCancel();
|
||||
} else if (key.backspace) {
|
||||
setSegmentsInput(segmentsInput.slice(0, -1));
|
||||
} else if (input && /\d/.test(input) && !key.ctrl) {
|
||||
} else if (shouldInsertInput(input, key) && /\d/.test(input)) {
|
||||
setSegmentsInput(segmentsInput + input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
WidgetEditorProps,
|
||||
WidgetItem
|
||||
} from '../types/Widget';
|
||||
import { shouldInsertInput } from '../utils/input-guards';
|
||||
|
||||
export class CustomCommandWidget implements Widget {
|
||||
getDefaultColor(): string { return 'white'; }
|
||||
@@ -160,7 +161,7 @@ const CustomCommandEditor: React.FC<WidgetEditorProps> = ({ widget, onComplete,
|
||||
if (commandCursorPos < commandInput.length) {
|
||||
setCommandInput(commandInput.slice(0, commandCursorPos) + commandInput.slice(commandCursorPos + 1));
|
||||
}
|
||||
} else if (input) {
|
||||
} else if (shouldInsertInput(input, key)) {
|
||||
setCommandInput(commandInput.slice(0, commandCursorPos) + input + commandInput.slice(commandCursorPos));
|
||||
setCommandCursorPos(commandCursorPos + input.length);
|
||||
}
|
||||
@@ -178,7 +179,7 @@ const CustomCommandEditor: React.FC<WidgetEditorProps> = ({ widget, onComplete,
|
||||
onCancel();
|
||||
} else if (key.backspace) {
|
||||
setWidthInput(widthInput.slice(0, -1));
|
||||
} else if (input && /\d/.test(input)) {
|
||||
} else if (shouldInsertInput(input, key) && /\d/.test(input)) {
|
||||
setWidthInput(widthInput + input);
|
||||
}
|
||||
} else if (mode === 'timeout') {
|
||||
@@ -195,7 +196,7 @@ const CustomCommandEditor: React.FC<WidgetEditorProps> = ({ widget, onComplete,
|
||||
onCancel();
|
||||
} else if (key.backspace) {
|
||||
setTimeoutInput(timeoutInput.slice(0, -1));
|
||||
} else if (input && /\d/.test(input)) {
|
||||
} else if (shouldInsertInput(input, key) && /\d/.test(input)) {
|
||||
setTimeoutInput(timeoutInput + input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
WidgetEditorProps,
|
||||
WidgetItem
|
||||
} from '../types/Widget';
|
||||
import { shouldInsertInput } from '../utils/input-guards';
|
||||
|
||||
export class CustomTextWidget implements Widget {
|
||||
getDefaultColor(): string { return 'white'; }
|
||||
@@ -129,7 +130,7 @@ const CustomTextEditor: React.FC<WidgetEditorProps> = ({ widget, onComplete, onC
|
||||
setText(text.slice(0, deleteFromIndex) + text.slice(deleteToIndex));
|
||||
}
|
||||
}
|
||||
} else if (input && !key.ctrl && !key.meta) {
|
||||
} else if (shouldInsertInput(input, key)) {
|
||||
// Insert the input at cursor position
|
||||
const newText = text.slice(0, cursorPos) + input + text.slice(cursorPos);
|
||||
setText(newText);
|
||||
|
||||
Reference in new issue
Block a user