mirror of
https://github.com/tiennm99/ccstatusline.git
synced 2026-09-02 16:19:21 +00:00
feat: Add fish-style path abbreviation to CurrentWorkingDir widget (#80)
* feat: Add fish-style path abbreviation to CurrentWorkingDir widget - Added new toggle option for fish-style path abbreviation - Keeps home directory as ~, first and last directories complete - Abbreviates middle directories to first letter only - Preserves dot for hidden directories (e.g. .config → .c) - Maintains backward compatibility with segments feature - Both options are mutually exclusive (fish-style takes precedence) Example: ~/Documents/Projects/my-project → ~/D/P/my-project 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Modify cwd widget fish-style code to use handleEditorAction instead of renderEditor, misc lint fixes --------- Co-authored-by: Jishuai <aqianlikuaizaifeng@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Matthew Breedlove <sirmalloc@gmail.com>
This commit is contained in:
co-authored by
Jishuai
Claude
Matthew Breedlove
parent
0a5f0e58b9
commit
d23af975b2
@@ -3,6 +3,7 @@ import {
|
||||
Text,
|
||||
useInput
|
||||
} from 'ink';
|
||||
import * as os from 'node:os';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import type { RenderContext } from '../types/RenderContext';
|
||||
@@ -21,9 +22,12 @@ export class CurrentWorkingDirWidget implements Widget {
|
||||
getDisplayName(): string { return 'Current Working Dir'; }
|
||||
getEditorDisplay(item: WidgetItem): WidgetEditorDisplay {
|
||||
const segments = item.metadata?.segments ? parseInt(item.metadata.segments, 10) : undefined;
|
||||
const fishStyle = item.metadata?.fishStyle === 'true';
|
||||
const modifiers: string[] = [];
|
||||
|
||||
if (segments && segments > 0) {
|
||||
if (fishStyle) {
|
||||
modifiers.push('fish-style');
|
||||
} else if (segments && segments > 0) {
|
||||
modifiers.push(`segments: ${segments}`);
|
||||
}
|
||||
|
||||
@@ -33,36 +37,74 @@ export class CurrentWorkingDirWidget implements Widget {
|
||||
};
|
||||
}
|
||||
|
||||
handleEditorAction(action: string, item: WidgetItem): WidgetItem | null {
|
||||
if (action === 'toggle-fish-style') {
|
||||
const currentFishStyle = item.metadata?.fishStyle === 'true';
|
||||
const newFishStyle = !currentFishStyle;
|
||||
|
||||
// Toggle fish style and clear segments
|
||||
if (newFishStyle) {
|
||||
// When enabling fish-style, clear segments
|
||||
const { segments, ...restMetadata } = item.metadata ?? {};
|
||||
void segments;
|
||||
return {
|
||||
...item,
|
||||
metadata: {
|
||||
...restMetadata,
|
||||
fishStyle: 'true'
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// When disabling fish-style
|
||||
const { fishStyle, ...restMetadata } = item.metadata ?? {};
|
||||
void fishStyle;
|
||||
|
||||
return {
|
||||
...item,
|
||||
metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
render(item: WidgetItem, context: RenderContext, settings: Settings): string | null {
|
||||
const segments = item.metadata?.segments ? parseInt(item.metadata.segments, 10) : undefined;
|
||||
const fishStyle = item.metadata?.fishStyle === 'true';
|
||||
|
||||
if (context.isPreview) {
|
||||
const segments = item.metadata?.segments ? parseInt(item.metadata.segments, 10) : undefined;
|
||||
let previewPath: string;
|
||||
if (segments && segments > 0) {
|
||||
|
||||
if (fishStyle) {
|
||||
previewPath = '~/D/P/my-project';
|
||||
} else if (segments && segments > 0) {
|
||||
if (segments === 1) {
|
||||
previewPath = '.../project';
|
||||
} else {
|
||||
previewPath = '.../example/project';
|
||||
}
|
||||
} else {
|
||||
previewPath = '/Users/example/project';
|
||||
previewPath = '/Users/example/Documents/Projects/my-project';
|
||||
}
|
||||
|
||||
return item.rawValue ? previewPath : `cwd: ${previewPath}`;
|
||||
}
|
||||
|
||||
const cwd = context.data?.cwd;
|
||||
if (!cwd) {
|
||||
if (!cwd)
|
||||
return null;
|
||||
}
|
||||
|
||||
const segments = item.metadata?.segments ? parseInt(item.metadata.segments, 10) : undefined;
|
||||
let displayPath = cwd;
|
||||
|
||||
if (segments && segments > 0) {
|
||||
if (fishStyle) {
|
||||
displayPath = this.abbreviatePath(cwd);
|
||||
} else if (segments && segments > 0) {
|
||||
// Support both POSIX ('/') and Windows ('\\') separators; preserve original separator in output
|
||||
const useBackslash = cwd.includes('\\') && !cwd.includes('/');
|
||||
const outSep = useBackslash ? '\\' : '/';
|
||||
|
||||
const pathParts = cwd.split(/[\\/]+/);
|
||||
|
||||
// Remove empty strings from splitting (e.g., leading slash or UNC leading separators)
|
||||
const filteredParts = pathParts.filter(part => part !== '');
|
||||
|
||||
@@ -78,7 +120,8 @@ export class CurrentWorkingDirWidget implements Widget {
|
||||
|
||||
getCustomKeybinds(): CustomKeybind[] {
|
||||
return [
|
||||
{ key: 's', label: '(s)egments', action: 'edit-segments' }
|
||||
{ key: 's', label: '(s)egments', action: 'edit-segments' },
|
||||
{ key: 'f', label: '(f)ish style', action: 'toggle-fish-style' }
|
||||
];
|
||||
}
|
||||
|
||||
@@ -88,6 +131,44 @@ export class CurrentWorkingDirWidget implements Widget {
|
||||
|
||||
supportsRawValue(): boolean { return true; }
|
||||
supportsColors(item: WidgetItem): boolean { return true; }
|
||||
|
||||
private abbreviatePath(path: string): string {
|
||||
const homeDir = os.homedir();
|
||||
const useBackslash = path.includes('\\') && !path.includes('/');
|
||||
const sep = useBackslash ? '\\' : '/';
|
||||
|
||||
// Replace home directory with ~
|
||||
let normalizedPath = path;
|
||||
if (path.startsWith(homeDir)) {
|
||||
normalizedPath = '~' + path.slice(homeDir.length);
|
||||
}
|
||||
|
||||
// Split path into parts
|
||||
const parts = normalizedPath.split(/[\\/]+/).filter(part => part !== '');
|
||||
|
||||
// Keep first and last parts full, abbreviate middle parts
|
||||
const abbreviated = parts.map((part, index) => {
|
||||
if (index === 0 || index === parts.length - 1) {
|
||||
return part; // Keep full
|
||||
}
|
||||
|
||||
// Hidden directories keep the dot
|
||||
if (part.startsWith('.') && part.length > 1) {
|
||||
return '.' + (part[1] ?? '');
|
||||
}
|
||||
|
||||
return part[0]; // Only first letter for others
|
||||
});
|
||||
|
||||
// Rebuild path
|
||||
if (normalizedPath.startsWith('~')) {
|
||||
return abbreviated.join(sep);
|
||||
} else if (normalizedPath.startsWith('/')) {
|
||||
return sep + abbreviated.join(sep);
|
||||
} else {
|
||||
return abbreviated.join(sep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CurrentWorkingDirEditor: React.FC<WidgetEditorProps> = ({ widget, onComplete, onCancel, action }) => {
|
||||
|
||||
Reference in New Issue
Block a user