Skip to content

Commit 931c41c

Browse files
authored
Merge pull request #315 from GCWing/refactor/file-tree-remove-git-indicators
refactor(file-system): remove Git status UI from file tree
2 parents 75306af + e4b522b commit 931c41c

File tree

26 files changed

+386
-867
lines changed

26 files changed

+386
-867
lines changed

src/crates/core/src/infrastructure/filesystem/file_operations.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -303,16 +303,21 @@ impl FileOperationService {
303303
}
304304

305305
pub async fn copy_file(&self, from: &str, to: &str) -> BitFunResult<u64> {
306-
let from_path = Path::new(from);
307-
let to_path = Path::new(to);
306+
let from_trim = from.trim();
307+
let to_trim = to.trim();
308+
let from_path = Path::new(from_trim);
309+
let to_path = Path::new(to_trim);
308310

309311
self.validate_file_access(from_path, false).await?;
310312
self.validate_file_access(to_path, true).await?;
311313

312-
if !from_path.exists() {
314+
// Use symlink_metadata (do not follow symlinks). `Path::exists()` follows links and
315+
// returns false for broken symlinks and some reparse-point / cloud placeholder edge cases
316+
// even though the name is listed in the directory.
317+
if fs::symlink_metadata(from_path).await.is_err() {
313318
return Err(BitFunError::service(format!(
314319
"Source file does not exist: {}",
315-
from
320+
from_trim
316321
)));
317322
}
318323

@@ -336,16 +341,18 @@ impl FileOperationService {
336341
}
337342

338343
pub async fn move_file(&self, from: &str, to: &str) -> BitFunResult<()> {
339-
let from_path = Path::new(from);
340-
let to_path = Path::new(to);
344+
let from_trim = from.trim();
345+
let to_trim = to.trim();
346+
let from_path = Path::new(from_trim);
347+
let to_path = Path::new(to_trim);
341348

342349
self.validate_file_access(from_path, true).await?;
343350
self.validate_file_access(to_path, true).await?;
344351

345-
if !from_path.exists() {
352+
if fs::symlink_metadata(from_path).await.is_err() {
346353
return Err(BitFunError::service(format!(
347354
"Source file does not exist: {}",
348-
from
355+
from_trim
349356
)));
350357
}
351358

src/web-ui/src/app/components/SceneBar/SceneBar.scss

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -96,26 +96,43 @@ $_tab-v-margin: 6px; // symmetric top/bottom gap inside SceneBar
9696
}
9797
}
9898

99-
&:only-child,
100-
&:only-child#{&}--active {
101-
cursor: default;
102-
}
103-
104-
// Active state
99+
// Active state — same fill as .bitfun-nav-panel__item.is-active (no left rail on horizontal tabs)
105100
&--active {
106101
color: var(--color-text-primary);
107102
font-weight: 500;
108-
background: var(--element-bg-base);
103+
background: var(--element-bg-soft);
109104

110105
.bitfun-scene-tab__icon {
111-
color: var(--color-primary);
106+
color: var(--color-text-primary);
107+
opacity: 1;
112108
}
113109

114110
&:hover .bitfun-scene-tab__close {
115111
opacity: 1;
116112
}
117113
}
118114

115+
// Single tab: no pill background (same whether active or not).
116+
&:only-child {
117+
cursor: default;
118+
background: transparent;
119+
box-shadow: none;
120+
121+
&:hover {
122+
background: transparent;
123+
color: var(--color-text-secondary);
124+
}
125+
126+
&.bitfun-scene-tab--active {
127+
background: transparent;
128+
box-shadow: none;
129+
130+
&:hover {
131+
background: transparent;
132+
}
133+
}
134+
}
135+
119136
&:focus-visible {
120137
outline: 2px solid var(--color-accent-500);
121138
outline-offset: -2px;
@@ -226,47 +243,61 @@ $_tab-v-margin: 6px; // symmetric top/bottom gap inside SceneBar
226243
opacity: 0.94;
227244
}
228245

229-
// Light themes: inactive tabs use tiered bg.* tokens; active is a gentle step brighter
230-
// (not full scene white + cast shadow) so it does not read as a separate floating card.
246+
// Light themes: inactive = former active (soft tint on chrome); active = former inactive (lifted pill + edge).
231247
:root[data-theme-type='light'] .bitfun-scene-tab {
232-
background: var(--color-bg-tertiary);
233-
color: var(--color-text-secondary);
248+
background: var(--element-bg-soft);
249+
color: var(--color-text-primary);
250+
box-shadow: none;
234251

235252
&:hover {
236-
background: var(--color-bg-quaternary);
237-
color: var(--color-text-secondary);
253+
background: var(--element-bg-medium);
254+
color: var(--color-text-primary);
255+
box-shadow: none;
256+
}
257+
258+
.bitfun-scene-tab__icon {
259+
opacity: 1;
238260
}
239261

240262
&--active {
241-
color: var(--color-text-primary);
242-
// Softer than full scene white + drop shadow: stays in the same family as idle tabs
243-
background: color-mix(in srgb, var(--color-bg-scene) 72%, var(--color-bg-tertiary));
244-
box-shadow: 0 0 0 1px color-mix(in srgb, var(--border-subtle) 42%, transparent);
263+
background: var(--color-bg-secondary);
264+
color: color-mix(in srgb, var(--color-text-primary) 86%, var(--color-text-muted));
265+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--border-subtle) 38%, transparent);
245266

246267
&:hover {
247-
background: color-mix(in srgb, var(--color-bg-scene) 76%, var(--color-bg-tertiary));
268+
background: color-mix(in srgb, var(--color-bg-secondary) 82%, var(--color-bg-quaternary));
269+
color: color-mix(in srgb, var(--color-text-primary) 90%, var(--color-text-muted));
270+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--border-subtle) 48%, transparent);
248271
}
249-
}
250272

251-
// Single tab: still a visible chip when inactive; when active, align with scene card.
252-
&:only-child:not(.bitfun-scene-tab--active) {
253-
background: var(--color-bg-tertiary);
254-
box-shadow: none;
255-
color: var(--color-text-secondary);
273+
.bitfun-scene-tab__icon {
274+
color: inherit;
275+
}
276+
277+
.bitfun-scene-tab__subtitle {
278+
color: var(--color-text-muted);
279+
}
256280
}
257281

258-
&:only-child.bitfun-scene-tab--active {
259-
background: color-mix(in srgb, var(--color-bg-scene) 72%, var(--color-bg-tertiary));
260-
box-shadow: 0 0 0 1px color-mix(in srgb, var(--border-subtle) 42%, transparent);
261-
color: var(--color-text-primary);
282+
&:only-child {
283+
background: transparent;
284+
box-shadow: none;
285+
color: color-mix(in srgb, var(--color-text-primary) 86%, var(--color-text-muted));
262286

263287
&:hover {
264-
background: color-mix(in srgb, var(--color-bg-scene) 76%, var(--color-bg-tertiary));
288+
background: transparent;
289+
color: color-mix(in srgb, var(--color-text-primary) 90%, var(--color-text-muted));
265290
}
266-
}
267291

268-
&:only-child:not(.bitfun-scene-tab--active):hover {
269-
background: var(--color-bg-quaternary);
292+
&.bitfun-scene-tab--active {
293+
background: transparent;
294+
box-shadow: none;
295+
color: var(--color-text-primary);
296+
297+
&:hover {
298+
background: transparent;
299+
}
300+
}
270301
}
271302
}
272303

src/web-ui/src/app/components/panels/FilesPanel.tsx

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
type FileExplorerToolbarHandlers,
1414
} from '@/tools/file-system';
1515
import { Search, IconButton, Tooltip } from '@/component-library';
16-
import { useFileTreeGitSync } from '@/tools/file-system/hooks/useFileTreeGitSync';
1716
import { FileSearchResults } from '@/tools/file-system/components/FileSearchResults';
1817
import { useFileSearch } from '@/hooks';
1918
import { workspaceAPI } from '@/infrastructure/api';
@@ -24,6 +23,7 @@ import { InputDialog, CubeLoading } from '@/component-library';
2423
import { openFileInBestTarget } from '@/shared/utils/tabUtils';
2524
import { PanelHeader } from './base';
2625
import { createLogger } from '@/shared/utils/logger';
26+
import { basenamePath, dirnameAbsolutePath, normalizeLocalPathForRename, replaceBasename } from '@/shared/utils/pathUtils';
2727
import { workspaceManager } from '@/infrastructure/services/business/workspaceManager';
2828
import { isRemoteWorkspace } from '@/shared/types';
2929
import {
@@ -108,7 +108,6 @@ const FilesPanel: React.FC<FilesPanelProps> = ({
108108
selectFile,
109109
expandFolder,
110110
expandFolderLazy,
111-
setFileTree
112111
} = useFileSystem({
113112
rootPath: workspacePath,
114113
autoLoad: true,
@@ -117,22 +116,10 @@ const FilesPanel: React.FC<FilesPanelProps> = ({
117116
enableAutoWatch: true,
118117
enableLazyLoad: true
119118
});
120-
const handleTreeUpdate = useCallback((updatedTree: FileSystemNode[]) => {
121-
log.debug('File tree updated', { nodeCount: updatedTree.length });
122-
setFileTree(updatedTree);
123-
}, [setFileTree]);
124-
125119
const handleNodeExpandLazy = useCallback((path: string) => {
126120
expandFolderLazy(path);
127121
}, [expandFolderLazy]);
128122

129-
useFileTreeGitSync({
130-
workspacePath,
131-
fileTree,
132-
onTreeUpdate: handleTreeUpdate,
133-
autoRefresh: true
134-
});
135-
136123
const prevWorkspacePathRef = useRef<string | undefined>(workspacePath);
137124
useEffect(() => {
138125
if (prevWorkspacePathRef.current !== undefined && prevWorkspacePathRef.current !== workspacePath) {
@@ -235,22 +222,19 @@ const FilesPanel: React.FC<FilesPanelProps> = ({
235222
}, []);
236223

237224
const handleExecuteRename = useCallback(async (oldPath: string, newName: string) => {
238-
const isWindows = oldPath.includes('\\');
239-
const separator = isWindows ? '\\' : '/';
240-
const pathParts = oldPath.split(separator);
241-
const oldName = pathParts[pathParts.length - 1];
242-
243-
if (newName === oldName) {
225+
const normalizedOld = normalizeLocalPathForRename(oldPath);
226+
const oldName = basenamePath(normalizedOld);
227+
228+
if (newName.trim() === oldName) {
244229
setRenamingPath(null);
245230
return;
246231
}
247-
248-
pathParts[pathParts.length - 1] = newName;
249-
const newPath = pathParts.join(separator);
250-
232+
233+
const newPath = replaceBasename(normalizedOld, newName.trim());
234+
251235
try {
252-
await workspaceAPI.renameFile(oldPath, newPath);
253-
log.info('File renamed', { oldPath, newPath });
236+
await workspaceAPI.renameFile(normalizedOld, newPath);
237+
log.info('File renamed', { oldPath: normalizedOld, newPath });
254238
setRenamingPath(null);
255239
loadFileTree(workspacePath || '', true);
256240
} catch (error) {
@@ -367,11 +351,7 @@ const FilesPanel: React.FC<FilesPanelProps> = ({
367351
}, [workspacePath, expandFolder, expandedFolders]);
368352

369353
const getParentDirectory = useCallback((filePath: string): string => {
370-
const isWindows = filePath.includes('\\');
371-
const separator = isWindows ? '\\' : '/';
372-
const parts = filePath.split(separator);
373-
parts.pop();
374-
return parts.join(separator);
354+
return dirnameAbsolutePath(filePath);
375355
}, []);
376356

377357
const findNode = useCallback((nodes: FileSystemNode[], path: string): FileSystemNode | null => {

src/web-ui/src/flow_chat/components/FileMentionPicker.scss

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,16 +247,29 @@
247247
}
248248

249249
&__item--selected {
250-
background: color-mix(in srgb, var(--color-accent-primary) 14%, white 86%);
251-
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-accent-primary) 30%, transparent);
250+
// Fallback when color-mix is unsupported; keeps keyboard highlight visible on light surfaces
251+
background: var(--element-bg-medium);
252+
background: color-mix(
253+
in srgb,
254+
var(--color-accent-primary, var(--color-accent-500)) 22%,
255+
var(--color-bg-elevated) 78%
256+
);
252257

253258
.file-mention-picker__item-name {
254-
color: color-mix(in srgb, var(--color-accent-primary) 78%, var(--color-text-primary));
259+
color: color-mix(
260+
in srgb,
261+
var(--color-accent-primary, var(--color-accent-500)) 72%,
262+
var(--color-text-primary)
263+
);
255264
}
256265

257266
.file-mention-picker__icon,
258267
.file-mention-picker__expand-icon {
259-
color: color-mix(in srgb, var(--color-accent-primary) 70%, var(--color-text-primary));
268+
color: color-mix(
269+
in srgb,
270+
var(--color-accent-primary, var(--color-accent-500)) 65%,
271+
var(--color-text-primary)
272+
);
260273
opacity: 0.92;
261274
}
262275
}

src/web-ui/src/infrastructure/theme/core/ThemeService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ export class ThemeService {
333333
root.style.setProperty('--color-primary', primaryAccent);
334334
root.style.setProperty('--color-primary-hover', primaryHover);
335335
root.style.setProperty('--color-accent', primaryAccent);
336+
root.style.setProperty('--color-accent-primary', primaryAccent);
336337
const primaryRgb = accentColorToRgbChannels(primaryAccent);
337338
if (primaryRgb) {
338339
root.style.setProperty('--color-primary-rgb', primaryRgb);

src/web-ui/src/locales/en-US/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,8 @@
685685
"renameSymbol": "Rename Symbol",
686686
"quickFix": "Quick Fix...",
687687
"goToSymbol": "Go to Symbol...",
688-
"highlightAllOccurrences": "Highlight All Occurrences"
688+
"highlightAllOccurrences": "Highlight All Occurrences",
689+
"addToChat": "Add to Chat Context"
689690
},
690691
"document": {
691692
"delete": "Delete Document"

src/web-ui/src/locales/zh-CN/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,8 @@
685685
"renameSymbol": "重命名符号",
686686
"quickFix": "快速修复...",
687687
"goToSymbol": "转到符号...",
688-
"highlightAllOccurrences": "高亮所有出现位置"
688+
"highlightAllOccurrences": "高亮所有出现位置",
689+
"addToChat": "添加到聊天上下文"
689690
},
690691
"document": {
691692
"delete": "删除文档"

src/web-ui/src/shared/context-menu-system/core/ContextResolver.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ export class ContextResolver {
243243

244244

245245
let cursorPosition: { line: number; column: number } | undefined;
246+
let selectionRange: EditorContext['selectionRange'];
246247

247248
try {
248249
const monacoGlobal = (window as any).monaco;
@@ -308,15 +309,32 @@ export class ContextResolver {
308309
};
309310
} else {
310311

311-
const selection = targetEditor.getSelection?.();
312-
if (selection) {
312+
const monacoSelection = targetEditor.getSelection?.();
313+
if (monacoSelection) {
313314
cursorPosition = {
314-
line: selection.startLineNumber,
315-
column: selection.startColumn
315+
line: monacoSelection.startLineNumber,
316+
column: monacoSelection.startColumn
316317
};
317318
}
318319
}
319320
}
321+
322+
const monacoSelection = targetEditor.getSelection?.();
323+
if (monacoSelection) {
324+
const isEmpty =
325+
typeof monacoSelection.isEmpty === 'function'
326+
? monacoSelection.isEmpty()
327+
: monacoSelection.startLineNumber === monacoSelection.endLineNumber &&
328+
monacoSelection.startColumn === monacoSelection.endColumn;
329+
if (!isEmpty) {
330+
selectionRange = {
331+
startLine: monacoSelection.startLineNumber,
332+
endLine: monacoSelection.endLineNumber,
333+
startColumn: monacoSelection.startColumn,
334+
endColumn: monacoSelection.endColumn
335+
};
336+
}
337+
}
320338
}
321339
}
322340

@@ -342,6 +360,7 @@ export class ContextResolver {
342360
filePath,
343361
cursorPosition,
344362
selectedText,
363+
selectionRange,
345364
isReadOnly
346365
};
347366
}

0 commit comments

Comments
 (0)