Skip to content

Commit 210338d

Browse files
committed
refactor: reduce cognitive complexity in parser and generator services
- extract parseKeyValue into smaller focused methods - add helper methods for key category detection - split environment variable handling into separate method - extract section formatting logic - reduce nesting and improve readability - all methods now under complexity threshold of 15
1 parent 6d1a640 commit 210338d

2 files changed

Lines changed: 164 additions & 79 deletions

File tree

src/services/kitty-generator.service.ts

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -71,75 +71,73 @@ export class KittyGeneratorService {
7171
private generateAdvancedSection(config: KittyConfigAST): string {
7272
const advanced = config.advanced as unknown as Record<string, unknown>;
7373
const defaults = DEFAULT_KITTY_CONFIG.advanced as unknown as Record<string, unknown>;
74-
let section = '';
7574
const lines: string[] = [];
7675

7776
for (const key in advanced) {
7877
if (!Object.hasOwn(advanced, key)) continue;
7978

8079
if (key === 'env') {
81-
const envRecord = advanced['env'] as Record<string, string>;
82-
const defaultEnvRecord = defaults['env'] as Record<string, string>;
83-
for (const envKey in envRecord) {
84-
if (!Object.hasOwn(envRecord, envKey)) continue;
85-
const val = envRecord[envKey];
86-
const defaultVal = defaultEnvRecord[envKey];
87-
if (val !== defaultVal) {
88-
lines.push(`env ${envKey}=${val}`);
89-
}
90-
}
80+
this.collectEnvLines(advanced, defaults, lines);
9181
continue;
9282
}
9383

94-
const currentValue = advanced[key];
95-
const defaultValue = defaults[key];
84+
this.collectConfigLine(key, advanced[key], defaults[key], lines);
85+
}
9686

97-
if (!this.isDifferent(currentValue, defaultValue)) continue;
87+
return this.formatSection('Advanced', lines);
88+
}
9889

99-
const formatted = this.formatValueForKey(key, currentValue);
100-
if (formatted !== null) {
101-
lines.push(...formatted);
90+
private collectEnvLines(
91+
advanced: Record<string, unknown>,
92+
defaults: Record<string, unknown>,
93+
lines: string[]
94+
): void {
95+
const envRecord = advanced['env'] as Record<string, string>;
96+
const defaultEnvRecord = defaults['env'] as Record<string, string>;
97+
98+
for (const envKey in envRecord) {
99+
if (!Object.hasOwn(envRecord, envKey)) continue;
100+
const val = envRecord[envKey];
101+
const defaultVal = defaultEnvRecord[envKey];
102+
if (val !== defaultVal) {
103+
lines.push(`env ${envKey}=${val}`);
102104
}
103105
}
106+
}
104107

105-
if (lines.length > 0) {
106-
section += '# --- Advanced ---\n';
107-
for (const line of lines) {
108-
section += `${line}\n`;
109-
}
110-
section += '\n';
108+
private collectConfigLine(
109+
key: string,
110+
currentValue: unknown,
111+
defaultValue: unknown,
112+
lines: string[]
113+
): void {
114+
if (!this.isDifferent(currentValue, defaultValue)) return;
115+
116+
const formatted = this.formatValueForKey(key, currentValue);
117+
if (formatted !== null) {
118+
lines.push(...formatted);
111119
}
120+
}
112121

113-
return section;
122+
private formatSection(title: string, lines: string[]): string {
123+
if (lines.length === 0) return '';
124+
125+
let section = `# --- ${title} ---\n`;
126+
for (const line of lines) {
127+
section += `${line}\n`;
128+
}
129+
return section + '\n';
114130
}
115131

116132
private generateSection(title: string, current: Record<string, unknown>, defaults: Record<string, unknown>): string {
117-
let section = '';
118133
const lines: string[] = [];
119134

120135
for (const key in current) {
121136
if (!Object.hasOwn(current, key)) continue;
122-
123-
const currentValue = current[key];
124-
const defaultValue = defaults[key];
125-
126-
if (!this.isDifferent(currentValue, defaultValue)) continue;
127-
128-
const formatted = this.formatValueForKey(key, currentValue);
129-
if (formatted !== null) {
130-
lines.push(...formatted);
131-
}
132-
}
133-
134-
if (lines.length > 0) {
135-
section += `# --- ${title} ---\n`;
136-
for (const line of lines) {
137-
section += `${line}\n`;
138-
}
139-
section += '\n';
137+
this.collectConfigLine(key, current[key], defaults[key], lines);
140138
}
141139

142-
return section;
140+
return this.formatSection(title, lines);
143141
}
144142

145143
private formatValueForKey(key: string, value: unknown): string[] | null {

src/services/kitty-parser.service.ts

Lines changed: 122 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -82,65 +82,152 @@ export class KittyParserService {
8282
}
8383

8484
private parseKeyValue(config: KittyConfigAST, key: string, value: string): void {
85-
if (key === 'map') {
86-
const parts = value.split(/\s+/);
87-
if (parts.length >= 2 && parts[0]) {
88-
config.keyboard_shortcuts.push({
89-
chord: parts[0],
90-
action: parts.slice(1).join(' ')
91-
});
92-
}
85+
if (this.handleSpecialKeys(config, key, value)) {
9386
return;
9487
}
88+
89+
this.routeToConfigSection(config, key, value);
90+
}
91+
92+
private handleSpecialKeys(config: KittyConfigAST, key: string, value: string): boolean {
93+
if (key === 'map') {
94+
this.parseKeyboardShortcut(config, value);
95+
return true;
96+
}
9597

9698
if (key === 'mouse_map') {
97-
const parts = value.split(/\s+/);
98-
if (parts.length >= 4 && parts[0] && parts[1] && parts[2]) {
99-
config.mouse_mappings.push({
100-
button: parts[0],
101-
event: parts[1],
102-
modes: parts[2],
103-
action: parts.slice(3).join(' ')
104-
});
105-
}
106-
return;
99+
this.parseMouseMapping(config, value);
100+
return true;
107101
}
108102

109103
if (key === 'env') {
110-
const eqIdx = value.indexOf('=');
111-
if (eqIdx > 0) {
112-
const envKey = value.slice(0, eqIdx).trim();
113-
const envVal = value.slice(eqIdx + 1);
114-
config.advanced.env[envKey] = envVal;
115-
}
116-
return;
104+
this.parseEnvironmentVariable(config, value);
105+
return true;
117106
}
118107

119-
if (key.startsWith('font_') || key === 'symbol_map' || key === 'narrow_symbols' || key === 'disable_ligatures' || key === 'force_ltr' || key === 'box_drawing_scale' || key === 'undercurl_style' || key === 'underline_exclusion' || key === 'text_composition_strategy' || key === 'text_fg_override_threshold' || key === 'modify_font' || key === 'bold_font' || key === 'italic_font' || key === 'bold_italic_font') {
108+
return false;
109+
}
110+
111+
private parseKeyboardShortcut(config: KittyConfigAST, value: string): void {
112+
const parts = value.split(/\s+/);
113+
if (parts.length >= 2 && parts[0]) {
114+
config.keyboard_shortcuts.push({
115+
chord: parts[0],
116+
action: parts.slice(1).join(' ')
117+
});
118+
}
119+
}
120+
121+
private parseMouseMapping(config: KittyConfigAST, value: string): void {
122+
const parts = value.split(/\s+/);
123+
if (parts.length >= 4 && parts[0] && parts[1] && parts[2]) {
124+
config.mouse_mappings.push({
125+
button: parts[0],
126+
event: parts[1],
127+
modes: parts[2],
128+
action: parts.slice(3).join(' ')
129+
});
130+
}
131+
}
132+
133+
private parseEnvironmentVariable(config: KittyConfigAST, value: string): void {
134+
const eqIdx = value.indexOf('=');
135+
if (eqIdx > 0) {
136+
const envKey = value.slice(0, eqIdx).trim();
137+
const envVal = value.slice(eqIdx + 1);
138+
config.advanced.env[envKey] = envVal;
139+
}
140+
}
141+
142+
private routeToConfigSection(config: KittyConfigAST, key: string, value: string): void {
143+
if (this.isFontKey(key)) {
120144
this.parseFontConfig(config, key, value);
121-
} else if (key.startsWith('cursor_') || key === 'cursor') {
145+
} else if (this.isCursorKey(key)) {
122146
this.parseCursorConfig(config, key, value);
123-
} else if (key.startsWith('scrollback_') || key === 'wheel_scroll_multiplier' || key === 'wheel_scroll_min_lines' || key === 'touch_scroll_multiplier') {
147+
} else if (this.isScrollbackKey(key)) {
124148
this.parseScrollbackConfig(config, key, value);
125-
} else if (key === 'mouse_hide_wait' || key === 'url_color' || key === 'url_style' || key === 'url_prefixes' || key === 'open_url_with' || key === 'detect_urls' || key === 'show_hyperlink_targets' || key === 'underline_hyperlinks' || key === 'copy_on_select' || key === 'paste_actions' || key === 'strip_trailing_spaces' || key === 'select_by_word_characters' || key === 'select_by_word_characters_forward' || key === 'click_interval' || key === 'focus_follows_mouse' || key.startsWith('pointer_shape') || key === 'default_pointer_shape') {
149+
} else if (this.isMouseKey(key)) {
126150
this.parseMouseConfig(config, key, value);
127-
} else if (key === 'repaint_delay' || key === 'input_delay' || key === 'sync_to_monitor') {
151+
} else if (this.isPerformanceKey(key)) {
128152
this.parsePerformanceConfig(config, key, value);
129-
} else if (key === 'enable_audio_bell' || key === 'visual_bell_duration' || key === 'visual_bell_color' || key === 'window_alert_on_bell' || key === 'bell_on_tab' || key === 'command_on_bell' || key === 'bell_path' || key === 'linux_bell_theme') {
153+
} else if (this.isBellKey(key)) {
130154
this.parseBellConfig(config, key, value);
131-
} else if (key === 'remember_window_size' || key === 'initial_window_width' || key === 'initial_window_height' || key === 'enabled_layouts' || key === 'placement_strategy' || key === 'hide_window_decorations' || key === 'window_border_width' || key === 'window_margin_width' || key === 'single_window_margin_width' || key === 'window_padding_width' || key === 'active_border_color' || key === 'inactive_border_color' || key === 'bell_border_color' || key === 'inactive_text_alpha' || key === 'draw_minimal_borders' || key === 'window_resize_step_cells' || key === 'window_resize_step_lines' || key === 'confirm_os_window_close' || key === 'window_logo_path' || key === 'window_logo_position' || key === 'window_logo_alpha' || key === 'resize_debounce_time' || key === 'resize_in_steps' || key === 'visual_window_select_characters') {
155+
} else if (this.isWindowLayoutKey(key)) {
132156
this.parseWindowLayoutConfig(config, key, value);
133-
} else if (key.startsWith('tab_') || key === 'active_tab_foreground' || key === 'active_tab_background' || key === 'active_tab_font_style' || key === 'inactive_tab_foreground' || key === 'inactive_tab_background' || key === 'inactive_tab_font_style') {
157+
} else if (this.isTabBarKey(key)) {
134158
this.parseTabBarConfig(config, key, value);
135-
} else if (key.startsWith('color') || key === 'foreground' || key === 'background' || key === 'background_opacity' || key === 'background_blur' || key === 'background_image' || key === 'background_image_layout' || key === 'background_image_linear' || key === 'background_tint' || key === 'background_tint_gaps' || key === 'dim_opacity' || key === 'selection_foreground' || key === 'selection_background' || key.startsWith('mark')) {
159+
} else if (this.isColorKey(key)) {
136160
this.parseColorConfig(config, key, value);
137-
} else if (key.startsWith('macos_') || key.startsWith('wayland_') || key.startsWith('linux_')) {
161+
} else if (this.isOSSpecificKey(key)) {
138162
this.parseOSSpecificConfig(config, key, value);
139163
} else {
140164
this.parseAdvancedConfig(config, key, value);
141165
}
142166
}
143167

168+
private isFontKey(key: string): boolean {
169+
return key.startsWith('font_') || ['symbol_map', 'narrow_symbols', 'disable_ligatures',
170+
'force_ltr', 'box_drawing_scale', 'undercurl_style', 'underline_exclusion',
171+
'text_composition_strategy', 'text_fg_override_threshold', 'modify_font',
172+
'bold_font', 'italic_font', 'bold_italic_font'].includes(key);
173+
}
174+
175+
private isCursorKey(key: string): boolean {
176+
return key.startsWith('cursor_') || key === 'cursor';
177+
}
178+
179+
private isScrollbackKey(key: string): boolean {
180+
return key.startsWith('scrollback_') || ['wheel_scroll_multiplier',
181+
'wheel_scroll_min_lines', 'touch_scroll_multiplier'].includes(key);
182+
}
183+
184+
private isMouseKey(key: string): boolean {
185+
return ['mouse_hide_wait', 'url_color', 'url_style', 'url_prefixes', 'open_url_with',
186+
'detect_urls', 'show_hyperlink_targets', 'underline_hyperlinks', 'copy_on_select',
187+
'paste_actions', 'strip_trailing_spaces', 'select_by_word_characters',
188+
'select_by_word_characters_forward', 'click_interval', 'focus_follows_mouse',
189+
'default_pointer_shape'].includes(key) || key.startsWith('pointer_shape');
190+
}
191+
192+
private isPerformanceKey(key: string): boolean {
193+
return ['repaint_delay', 'input_delay', 'sync_to_monitor'].includes(key);
194+
}
195+
196+
private isBellKey(key: string): boolean {
197+
return ['enable_audio_bell', 'visual_bell_duration', 'visual_bell_color',
198+
'window_alert_on_bell', 'bell_on_tab', 'command_on_bell', 'bell_path',
199+
'linux_bell_theme'].includes(key);
200+
}
201+
202+
private isWindowLayoutKey(key: string): boolean {
203+
return ['remember_window_size', 'initial_window_width', 'initial_window_height',
204+
'enabled_layouts', 'placement_strategy', 'hide_window_decorations',
205+
'window_border_width', 'window_margin_width', 'single_window_margin_width',
206+
'window_padding_width', 'active_border_color', 'inactive_border_color',
207+
'bell_border_color', 'inactive_text_alpha', 'draw_minimal_borders',
208+
'window_resize_step_cells', 'window_resize_step_lines', 'confirm_os_window_close',
209+
'window_logo_path', 'window_logo_position', 'window_logo_alpha',
210+
'resize_debounce_time', 'resize_in_steps', 'visual_window_select_characters'].includes(key);
211+
}
212+
213+
private isTabBarKey(key: string): boolean {
214+
return key.startsWith('tab_') || ['active_tab_foreground', 'active_tab_background',
215+
'active_tab_font_style', 'inactive_tab_foreground', 'inactive_tab_background',
216+
'inactive_tab_font_style'].includes(key);
217+
}
218+
219+
private isColorKey(key: string): boolean {
220+
return key.startsWith('color') || key.startsWith('mark') ||
221+
['foreground', 'background', 'background_opacity', 'background_blur',
222+
'background_image', 'background_image_layout', 'background_image_linear',
223+
'background_tint', 'background_tint_gaps', 'dim_opacity',
224+
'selection_foreground', 'selection_background'].includes(key);
225+
}
226+
227+
private isOSSpecificKey(key: string): boolean {
228+
return key.startsWith('macos_') || key.startsWith('wayland_') || key.startsWith('linux_');
229+
}
230+
144231
private parseFontConfig(config: KittyConfigAST, key: string, value: string): void {
145232
switch (key) {
146233
case 'font_family':

0 commit comments

Comments
 (0)