Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/renderer/components/files/CurrentFilePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ export function CurrentFilePanel({ rootPath, isActive = false }: CurrentFilePane
[tabs, promptUnsaved, saveFile, closeFile, t]
);

const closeSavedTabs = useCallback(async () => {
const paths = tabs.filter((tab) => !tab.isDirty).map((tab) => tab.path);
for (const path of paths) {
await closeFile(path);
}
}, [tabs, closeFile]);

// Handle tab click
const handleTabClick = useCallback(
(path: string) => {
Expand Down Expand Up @@ -266,6 +273,7 @@ export function CurrentFilePanel({ rootPath, isActive = false }: CurrentFilePane
const paths = tabs.map((t) => t.path);
await requestCloseTabs(paths);
}}
onCloseSaved={closeSavedTabs}
onCloseLeft={async (path) => {
const index = tabs.findIndex((t) => t.path === path);
if (index <= 0) return;
Expand Down
56 changes: 53 additions & 3 deletions src/renderer/components/files/EditorArea.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import Editor, { type OnMount } from '@monaco-editor/react';
import { ChevronRight, Eye, EyeOff, FileCode, FileX, Maximize2, MessageSquare } from 'lucide-react';
import {
ChevronRight,
Eye,
EyeOff,
FileCode,
FileX,
Maximize2,
MessageSquare,
MoreVertical,
} from 'lucide-react';
import type * as monaco from 'monaco-editor';
import {
forwardRef,
Expand All @@ -18,13 +27,15 @@ import {
BreadcrumbList,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import { Button } from '@/components/ui/button';
import {
Empty,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from '@/components/ui/empty';
import { Menu, MenuItem, MenuPopup, MenuTrigger } from '@/components/ui/menu';
import { addToast } from '@/components/ui/toast';
import { useDebouncedSave } from '@/hooks/useDebouncedSave';
import { useI18n } from '@/i18n';
Expand Down Expand Up @@ -139,6 +150,7 @@ interface EditorAreaProps {
onTabClose: (path: string) => void | Promise<void>;
onCloseOthers?: (keepPath: string) => void | Promise<void>;
onCloseAll?: () => void | Promise<void>;
onCloseSaved?: () => void | Promise<void>;
onCloseLeft?: (path: string) => void | Promise<void>;
onCloseRight?: (path: string) => void | Promise<void>;
onTabReorder: (fromIndex: number, toIndex: number) => void;
Expand All @@ -163,6 +175,7 @@ export const EditorArea = forwardRef<EditorAreaRef, EditorAreaProps>(function Ed
onTabClose,
onCloseOthers,
onCloseAll,
onCloseSaved,
onCloseLeft,
onCloseRight,
onTabReorder,
Expand Down Expand Up @@ -1287,7 +1300,7 @@ export const EditorArea = forwardRef<EditorAreaRef, EditorAreaProps>(function Ed
return (
<div className="flex h-full flex-col">
{/* Tabs */}
<div className="flex items-center">
<div className="flex h-10 items-center border-b">
{isFileTreeCollapsed && onToggleFileTree && (
<button
type="button"
Expand All @@ -1307,19 +1320,56 @@ export const EditorArea = forwardRef<EditorAreaRef, EditorAreaProps>(function Ed
onClose={onTabClose}
onCloseOthers={onCloseOthers}
onCloseAll={onCloseAll}
onCloseSaved={onCloseSaved}
onCloseLeft={onCloseLeft}
onCloseRight={onCloseRight}
onTabReorder={onTabReorder}
onSendToSession={sessionId ? handleSendToSession : undefined}
sessionId={sessionId}
/>
</div>
{tabs.length > 0 && (onCloseSaved || onCloseAll) && (
<Menu>
<MenuTrigger
render={
<Button
type="button"
variant="ghost"
size="sm"
className="h-9 w-9 shrink-0 rounded-none p-0 text-muted-foreground hover:bg-accent/50 hover:text-foreground"
title={t('Tab actions')}
>
<MoreVertical className="h-4 w-4" />
</Button>
}
/>
<MenuPopup align="end" side="bottom" sideOffset={4}>
<MenuItem
disabled={!onCloseSaved || !tabs.some((tab) => !tab.isDirty)}
onClick={async () => {
if (onCloseSaved) await onCloseSaved();
}}
>
{t('Close Saved Tabs')}
</MenuItem>
<MenuItem
disabled={!onCloseAll}
onClick={async () => {
if (!onCloseAll) return;
await onCloseAll();
}}
>
{t('Close All Tabs')}
</MenuItem>
</MenuPopup>
</Menu>
)}
{/* Markdown Preview Toggle */}
{isMarkdown && (
<button
type="button"
onClick={cyclePreviewMode}
className="flex h-10 w-10 shrink-0 items-center justify-center border-b text-muted-foreground hover:text-foreground transition-colors"
className="flex h-9 w-9 shrink-0 items-center justify-center text-muted-foreground transition-colors hover:text-foreground"
title={
previewMode === 'off'
? t('Show split preview')
Expand Down
15 changes: 14 additions & 1 deletion src/renderer/components/files/EditorTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface EditorTabsProps {
onClose?: (path: string) => void | Promise<void>;
onCloseOthers?: (keepPath: string) => void | Promise<void>;
onCloseAll?: () => void | Promise<void>;
onCloseSaved?: () => void | Promise<void>;
onCloseLeft?: (path: string) => void | Promise<void>;
onCloseRight?: (path: string) => void | Promise<void>;
onTabReorder?: (fromIndex: number, toIndex: number) => void;
Expand All @@ -34,6 +35,7 @@ export function EditorTabs({
onClose,
onCloseOthers,
onCloseAll,
onCloseSaved,
onCloseLeft,
onCloseRight,
onTabReorder,
Expand Down Expand Up @@ -86,6 +88,7 @@ export function EditorTabs({

const canCloseOthers = !!onCloseOthers && !!menuTabPath && tabs.length > 1;
const canCloseAll = !!onCloseAll && tabs.length > 0;
const canCloseSaved = !!onCloseSaved && tabs.some((tab) => !tab.isDirty);
const canCloseLeft = !!onCloseLeft && menuTabIndex > 0;
const canCloseRight = !!onCloseRight && menuTabIndex >= 0 && menuTabIndex < tabs.length - 1;

Expand Down Expand Up @@ -116,7 +119,7 @@ export function EditorTabs({
}

return (
<div className="h-10 shrink-0 overflow-hidden border-b">
<div className="h-10 shrink-0 overflow-hidden">
<ScrollArea className="h-full">
<div className="flex h-9 w-max pb-1">
{tabs.map((tab, index) => {
Expand Down Expand Up @@ -239,6 +242,16 @@ export function EditorTabs({
{t('Close Tabs to the Right')}
</MenuItem>
<MenuSeparator />
<MenuItem
disabled={!canCloseSaved}
onClick={async () => {
if (!onCloseSaved) return;
await onCloseSaved();
setMenuOpen(false);
}}
>
{t('Close Saved Tabs')}
</MenuItem>
<MenuItem
disabled={!canCloseAll}
onClick={async () => {
Expand Down
8 changes: 8 additions & 0 deletions src/renderer/components/files/FilePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,13 @@ export function FilePanel({ rootPath, isActive = false }: FilePanelProps) {
[tabs, promptUnsaved, saveFile, closeFile, t]
);

const closeSavedTabs = useCallback(async () => {
const paths = tabs.filter((tab) => !tab.isDirty).map((tab) => tab.path);
for (const path of paths) {
await closeFile(path);
}
}, [tabs, closeFile]);

// Handle file click (single click = open in editor)
const handleFileClick = useCallback(
(path: string) => {
Expand Down Expand Up @@ -795,6 +802,7 @@ export function FilePanel({ rootPath, isActive = false }: FilePanelProps) {
const paths = tabs.map((t) => t.path);
await requestCloseTabs(paths);
}}
onCloseSaved={closeSavedTabs}
onCloseLeft={async (path) => {
const index = tabs.findIndex((t) => t.path === path);
if (index <= 0) return;
Expand Down
2 changes: 2 additions & 0 deletions src/shared/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ export const zhTranslations: Record<string, string> = {
'Close Tabs to the Left': '关闭左侧所有',
'Close Tabs to the Right': '关闭右侧所有',
'Close All Tabs': '关闭所有',
'Close Saved Tabs': '关闭已保存',
'Tab actions': '标签页操作',
Collapse: '折叠',
'Collapse all': '折叠所有',
'Collapse all folders': '折叠所有文件夹',
Expand Down
Loading