mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
feat: Tab completion now respects shell current working directory
- Tab completion file/folder suggestions now show items relative to the shell's current directory (shellCwd) instead of the project root - Added shellRelativePath computation to navigate the file tree to the correct subdirectory before showing suggestions - Also includes: PTY terminal environment fix for proper PATH handling, and CSS fix to hide empty paragraphs in markdown prose Claude ID: 0302fea8-0c56-49fc-8134-c463a0ea1096 Maestro ID: b9bc0d08-5be2-4fdf-93cd-5618a8d53b35
This commit is contained in:
@@ -156,12 +156,33 @@ export class ProcessManager extends EventEmitter {
|
||||
ptyArgs = finalArgs;
|
||||
}
|
||||
|
||||
// Build environment for PTY process
|
||||
// For terminal sessions, we want the shell to build its own PATH from startup files
|
||||
// (.zprofile, .zshrc, etc.) rather than inheriting Electron's limited PATH.
|
||||
// Electron launched from Finder gets a minimal PATH from launchd, not the user's shell PATH.
|
||||
let ptyEnv: NodeJS.ProcessEnv;
|
||||
if (isTerminal) {
|
||||
// For terminals: pass minimal env, let shell startup files set PATH
|
||||
// This ensures the terminal has the same environment as a regular terminal app
|
||||
ptyEnv = {
|
||||
HOME: process.env.HOME,
|
||||
USER: process.env.USER,
|
||||
SHELL: process.env.SHELL,
|
||||
TERM: 'xterm-256color',
|
||||
LANG: process.env.LANG || 'en_US.UTF-8',
|
||||
// Don't pass PATH - let the shell build it from scratch via .zprofile/.zshrc
|
||||
};
|
||||
} else {
|
||||
// For AI agents in PTY mode: pass full env (they need NODE_PATH, etc.)
|
||||
ptyEnv = process.env;
|
||||
}
|
||||
|
||||
const ptyProcess = pty.spawn(ptyCommand, ptyArgs, {
|
||||
name: 'xterm-256color',
|
||||
cols: 100,
|
||||
rows: 30,
|
||||
cwd: cwd,
|
||||
env: process.env as any,
|
||||
env: ptyEnv as any,
|
||||
});
|
||||
|
||||
const managedProcess: ManagedProcess = {
|
||||
|
||||
@@ -543,6 +543,7 @@ const LogItemComponent = memo(({
|
||||
.prose h5 { color: ${theme.colors.textMain}; font-size: 1.2em; font-weight: bold; margin: 0; line-height: 1.4; }
|
||||
.prose h6 { color: ${theme.colors.textDim}; font-size: 1.1em; font-weight: bold; margin: 0; line-height: 1.4; }
|
||||
.prose p { color: ${theme.colors.textMain}; margin: 0; line-height: 1.4; }
|
||||
.prose p:empty { display: none; }
|
||||
.prose > ul, .prose > ol { color: ${theme.colors.textMain}; margin: 0.5em 0; padding-left: 2em; }
|
||||
.prose li ul, .prose li ol { margin: 0 !important; padding-left: 1.5em; }
|
||||
.prose li { margin: 0 !important; padding: 0; line-height: 1.4; display: list-item; }
|
||||
@@ -694,6 +695,7 @@ const LogItemComponent = memo(({
|
||||
.prose h5 { color: ${theme.colors.textMain}; font-size: 1.2em; font-weight: bold; margin: 0; line-height: 1.4; }
|
||||
.prose h6 { color: ${theme.colors.textDim}; font-size: 1.1em; font-weight: bold; margin: 0; line-height: 1.4; }
|
||||
.prose p { color: ${theme.colors.textMain}; margin: 0; line-height: 1.4; }
|
||||
.prose p:empty { display: none; }
|
||||
.prose > ul, .prose > ol { color: ${theme.colors.textMain}; margin: 0.5em 0; padding-left: 2em; }
|
||||
.prose li ul, .prose li ol { margin: 0 !important; padding-left: 1.5em; }
|
||||
.prose li { margin: 0 !important; padding: 0; line-height: 1.4; display: list-item; }
|
||||
@@ -827,6 +829,7 @@ const LogItemComponent = memo(({
|
||||
.prose h5 { color: ${theme.colors.textMain}; font-size: 1.2em; font-weight: bold; margin: 0; line-height: 1.4; }
|
||||
.prose h6 { color: ${theme.colors.textDim}; font-size: 1.1em; font-weight: bold; margin: 0; line-height: 1.4; }
|
||||
.prose p { color: ${theme.colors.textMain}; margin: 0; line-height: 1.4; }
|
||||
.prose p:empty { display: none; }
|
||||
.prose > ul, .prose > ol { color: ${theme.colors.textMain}; margin: 0.5em 0; padding-left: 2em; }
|
||||
.prose li ul, .prose li ol { margin: 0 !important; padding-left: 1.5em; }
|
||||
.prose li { margin: 0 !important; padding: 0; line-height: 1.4; display: list-item; }
|
||||
|
||||
@@ -17,7 +17,7 @@ export interface UseTabCompletionReturn {
|
||||
/**
|
||||
* Hook for providing tab completion suggestions from:
|
||||
* 1. Shell command history
|
||||
* 2. Current directory file tree
|
||||
* 2. Current directory file tree (relative to shell CWD)
|
||||
* 3. Git branches and tags (for git commands in git repos)
|
||||
*
|
||||
* Performance optimizations:
|
||||
@@ -26,10 +26,32 @@ export interface UseTabCompletionReturn {
|
||||
* - getSuggestions is wrapped in useCallback to maintain referential equality
|
||||
*/
|
||||
export function useTabCompletion(session: Session | null): UseTabCompletionReturn {
|
||||
// Compute relative path from project root (cwd) to shell working directory (shellCwd)
|
||||
const shellRelativePath = useMemo(() => {
|
||||
if (!session?.cwd || !session?.shellCwd) return '';
|
||||
|
||||
// Normalize paths
|
||||
const projectRoot = session.cwd.replace(/\/$/, '');
|
||||
const shellDir = session.shellCwd.replace(/\/$/, '');
|
||||
|
||||
// If shell is at project root, no relative path needed
|
||||
if (shellDir === projectRoot) return '';
|
||||
|
||||
// If shell is within project, compute relative path
|
||||
if (shellDir.startsWith(projectRoot + '/')) {
|
||||
return shellDir.slice(projectRoot.length + 1);
|
||||
}
|
||||
|
||||
// Shell is outside project root - can't use file tree
|
||||
return null;
|
||||
}, [session?.cwd, session?.shellCwd]);
|
||||
|
||||
// Build a flat list of file/folder names from the file tree
|
||||
// Only re-computed when fileTree actually changes
|
||||
// Filtered to show only files relative to the shell's current working directory
|
||||
const fileNames = useMemo(() => {
|
||||
if (!session?.fileTree) return [];
|
||||
// If shell is outside project, return empty
|
||||
if (shellRelativePath === null) return [];
|
||||
|
||||
const names: { name: string; type: 'file' | 'folder'; path: string }[] = [];
|
||||
|
||||
@@ -47,9 +69,31 @@ export function useTabCompletion(session: Session | null): UseTabCompletionRetur
|
||||
}
|
||||
};
|
||||
|
||||
traverse(session.fileTree);
|
||||
// If we have a relative path, find that subtree first
|
||||
if (shellRelativePath) {
|
||||
const pathParts = shellRelativePath.split('/');
|
||||
let currentNodes: FileNode[] = session.fileTree;
|
||||
|
||||
// Navigate to the shell's current directory in the tree
|
||||
for (const part of pathParts) {
|
||||
const found = currentNodes.find(n => n.name === part && n.type === 'folder');
|
||||
if (found && found.children) {
|
||||
currentNodes = found.children;
|
||||
} else {
|
||||
// Directory not found in tree - return empty
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse from the shell's current directory
|
||||
traverse(currentNodes);
|
||||
} else {
|
||||
// Shell is at project root - traverse entire tree
|
||||
traverse(session.fileTree);
|
||||
}
|
||||
|
||||
return names;
|
||||
}, [session?.fileTree]);
|
||||
}, [session?.fileTree, shellRelativePath]);
|
||||
|
||||
// Memoize shell history reference to avoid unnecessary getSuggestions re-creation
|
||||
const shellHistory = useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user