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
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
restoreProjectUiState,
} from "@/features/window/stores/workspace-ui-session";
import { useWorkspaceTabsStore } from "@/features/window/stores/workspace-tabs.store";
import { getProjectDisplayLabel } from "@/features/window/utils/project-display-label";
import { createAppWindow } from "@/features/window/utils/create-app-window";
import { serializeTerminals } from "@/features/terminal/lib/terminal-session-storage";
import { useTerminalTabsStore } from "@/features/terminal/stores/terminal-tabs.store";
Expand Down Expand Up @@ -641,34 +642,36 @@ const openLocalWorkspace = async (
state.isFileTreeLoading = true;
});

const projectName = getFolderName(path);
const folderName = getFolderName(path);

const readDirectoryStartedAt = performance.now();
logWorkspaceOpenStep("start", "readDirectoryContents", path);
const entries = await readDirectoryContents(path);
logWorkspaceOpenStep("end", "readDirectoryContents", path, readDirectoryStartedAt);

const fileTree = sortFileEntries(entries);
const wrappedFileTree = wrapWithRootFolder(fileTree, path, projectName);
const wrappedFileTree = wrapWithRootFolder(fileTree, path, folderName);

if (treeState === "expand-root") {
fileTreeStore.getState().actions.setExpandedPaths(new Set([path]));
} else {
fileTreeStore.getState().actions.collapseAll();
}

const workspaceTab = useWorkspaceTabsStore
.getState()
.projectTabs.find((projectTab) => projectTab.id === workspaceId);
const displayName = workspaceTab ? getProjectDisplayLabel(workspaceTab) : folderName;

const { setRootFolderPath, setProjectName, setActiveProjectId } =
projectStore.getState().actions;
setRootFolderPath(path);
setProjectName(projectName);
setProjectName(displayName);

if (restoreUiState) {
restoreProjectUiState(path, workspaceId);
}

const workspaceTab = useWorkspaceTabsStore
.getState()
.projectTabs.find((projectTab) => projectTab.id === workspaceId);
setActiveProjectId(workspaceId);
if (!prewarm) {
useRecentFoldersStore.getState().actions.addToRecents(path, {
Expand All @@ -683,7 +686,7 @@ const openLocalWorkspace = async (
state.isFileTreeLoading = false;
state.files = wrappedFileTree;
state.rootFolderPath = path;
state.workspaceFolders = [{ path, name: projectName, isPrimary: true }];
state.workspaceFolders = [{ path, name: folderName, isPrimary: true }];
state.filesVersion++;
state.projectFilesCache = undefined;
});
Expand Down Expand Up @@ -1432,6 +1435,9 @@ const createFileSystemStore = (workspaceId: string): StoreApi<ScopedFileSystemSt
// Add project to workspace tabs
useWorkspaceTabsStore.getState().actions.addProjectTab(remotePath, connection.name);
const activeProjectTab = useWorkspaceTabsStore.getState().actions.getActiveProjectTab();
const displayName = activeProjectTab
? getProjectDisplayLabel(activeProjectTab)
: connection.name;

// Initialize tree UI state: expand remote root
useFileTreeStore
Expand All @@ -1444,7 +1450,7 @@ const createFileSystemStore = (workspaceId: string): StoreApi<ScopedFileSystemSt
.getStore(workspaceId)
.getState().actions;
setRootFolderPath(remotePath);
setProjectName(connection.name);
setProjectName(displayName);
setActiveProjectId(activeProjectTab?.id);
restoreProjectUiState(remotePath, workspaceId);

Expand Down Expand Up @@ -1517,6 +1523,9 @@ const createFileSystemStore = (workspaceId: string): StoreApi<ScopedFileSystemSt

useWorkspaceTabsStore.getState().actions.addProjectTab(wslPath, projectName);
const activeProjectTab = useWorkspaceTabsStore.getState().actions.getActiveProjectTab();
const displayName = activeProjectTab
? getProjectDisplayLabel(activeProjectTab)
: projectName;
useFileTreeStore
.getStore(workspaceId)
.getState()
Expand All @@ -1526,7 +1535,7 @@ const createFileSystemStore = (workspaceId: string): StoreApi<ScopedFileSystemSt
.getStore(workspaceId)
.getState().actions;
setRootFolderPath(wslPath);
setProjectName(projectName);
setProjectName(displayName);
setActiveProjectId(activeProjectTab?.id);
restoreProjectUiState(wslPath, workspaceId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { isWslPath } from "@/features/wsl/utils/wsl-path";
import { useTerminalStore } from "@/features/terminal/stores/terminal.store";
import { useMenuEventsWrapper } from "@/features/window/hooks/use-menu-events-wrapper";
import { useWorkspaceTabsStore } from "@/features/window/stores/workspace-tabs.store";
import { getProjectDisplayLabel } from "@/features/window/utils/project-display-label";
import { useUIState } from "@/features/window/stores/ui-state.store";
import { toast } from "sonner";
import { useTranslation } from "@/i18n/locale-provider";
Expand Down Expand Up @@ -185,7 +186,9 @@ export function MainLayout() {
}

useWorkspaceTabsStore.getState().actions.removeProjectTab(activeTab.id);
toast.warning(t("fileSystem.removedMissingProject", { name: activeTab.name }));
toast.warning(
t("fileSystem.removedMissingProject", { name: getProjectDisplayLabel(activeTab) }),
);
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useState } from "react";
import { useFileSystemStore } from "@/features/file-system/stores/file-system.store";
import ProjectIconPicker from "@/features/window/components/project-icon-picker";
import { promptProjectDisplayAlias } from "@/features/window/controllers/prompt-project-display-alias";
import type { ProjectTab } from "@/features/window/stores/workspace-tabs.store";
import { getProjectDisplayLabel } from "@/features/window/utils/project-display-label";
import { createAppWindow } from "@/features/window/utils/create-app-window";
import {
ContextMenu,
Expand All @@ -15,6 +17,7 @@ import {
FolderOpenIcon,
ImageIcon,
OpenExternalIcon,
PencilSimpleLineIcon,
TrashIcon,
WindowExpandIcon,
} from "@/ui/icons";
Expand Down Expand Up @@ -55,6 +58,7 @@ export function SidebarProjectDots({
{projects.map((project) => {
const isRemote = isRemoteProjectPath(project.path);
const isActive = project.id === activeProjectId;
const displayName = getProjectDisplayLabel(project);

return (
<ContextMenu key={project.id}>
Expand All @@ -67,8 +71,8 @@ export function SidebarProjectDots({
)}
aria-label={
isActive
? t("titleProject.currentProject", { project: project.name })
: t("titleProject.switchToProject", { project: project.name })
? t("titleProject.currentProject", { project: displayName })
: t("titleProject.switchToProject", { project: displayName })
}
aria-current={isActive ? "page" : undefined}
aria-disabled={isSwitchingProject}
Expand Down Expand Up @@ -138,6 +142,10 @@ export function SidebarProjectDots({
{t("titleProject.selectIcon")}
</ContextMenuItem>
) : null}
<ContextMenuItem onClick={() => void promptProjectDisplayAlias(project)}>
<PencilSimpleLineIcon />
{t("titleProject.setDisplayAlias")}
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
variant="destructive"
Expand Down
3 changes: 2 additions & 1 deletion windows/tauri/src/features/run/api/run-core-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ const executeCore = mock(async () => ({
ok: true as const,
data: { document: "{}" },
}));
const cancelCoreOperation = mock(async () => true);

mock.module("@/core/lithe-core-client", () => ({ executeCore }));
mock.module("@/core/lithe-core-client", () => ({ executeCore, cancelCoreOperation }));

const { createLaunchPlan, saveRunConfigurationEditorChanges } = await import("./run-core-api");

Expand Down
13 changes: 9 additions & 4 deletions windows/tauri/src/features/run/api/run-host-api.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { Channel } from "@tauri-apps/api/core";
import { beforeEach, describe, expect, mock, test } from "bun:test";

const invoke = mock(async () => undefined);
const getCurrentWebviewWindow = mock(() => ({ label: "project-window" }));

mock.module("@/platform/tauri-core", () => ({ invoke }));
mock.module("@tauri-apps/api/webviewWindow", () => ({ getCurrentWebviewWindow }));
mock.module("@/platform/tauri-core", () => ({
invoke,
Channel,
convertFileSrc: (path: string) => path,
}));
mock.module("../utils/run-window-context", () => ({
getRunWindowLabel: () => "project-window",
}));

const { startRunProcess, stopRunProcess, writeRunStdin } = await import("../api/run-host-api");

describe("run host API window scoping", () => {
beforeEach(() => {
invoke.mockClear();
getCurrentWebviewWindow.mockClear();
});

test("startRunProcess includes the current window label", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const appendOutput = mock(() => undefined);
const finishProcess = mock(() => undefined);
const releaseRunSessionWorkspace = mock(() => undefined);

const actualRunStore = await import("../stores/run.store");

mock.module("@tauri-apps/api/webviewWindow", () => ({
getCurrentWebviewWindow: () => ({
label: "project-window",
Expand All @@ -20,6 +22,7 @@ mock.module("@tauri-apps/api/webviewWindow", () => ({
}));
mock.module("@tauri-apps/api/event", () => ({ listen: globalListen }));
mock.module("../stores/run.store", () => ({
...actualRunStore,
runStoreForSession: () => ({
getState: () => ({
actions: { appendOutput, finishProcess },
Expand Down
7 changes: 2 additions & 5 deletions windows/tauri/src/features/run/stores/run.store.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { describe, expect, mock, test } from "bun:test";

mock.module("@/platform/tauri-core", () => ({
invoke: mock(async () => undefined),
}));
import { describe, expect, test } from "bun:test";

// Window scoping is mocked in run-host-api.test.ts for the full `bun test src/features/run` suite.
const { createRunStore } = await import("./run.store");
const { PRIMARY_SESSION_ID } = await import("../types/run.types");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test("project tab bar exposes accessible switchable and closable project tabs",
expect(source).toContain("event.stopPropagation()");
expect(source).toContain("await closeProject(projectId)");
expect(source).toContain("finally");
expect(source).toContain('t("titleProject.closeProject", { name: project.name })');
expect(source).toContain('t("titleProject.closeProject", { name: displayName })');
expect(source).toContain("group-hover:opacity-100");
expect(source).toContain("group-focus-within:opacity-100");
expect(source).toContain(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useWorkspaceTabsStore } from "@/features/window/stores/workspace-tabs.s
import { Button } from "@/ui/button";
import { FolderIcon, XIcon as X } from "@/ui/icons";
import { cn } from "@/utils/cn";
import { getProjectDisplayLabel } from "../utils/project-display-label";
import { getProjectTabBarItems, shouldShowProjectTabBar } from "../utils/project-tab-bar-model";

interface ProjectTabBarProps {
Expand Down Expand Up @@ -43,7 +44,8 @@ export function ProjectTabBar({ hideWhenSingle = false }: ProjectTabBarProps) {
>
<div className="flex min-w-max items-center gap-1">
{projects.map((project) => {
const closeLabel = t("titleProject.closeProject", { name: project.name });
const displayName = getProjectDisplayLabel(project);
const closeLabel = t("titleProject.closeProject", { name: displayName });

return (
<div key={project.id} className="group relative">
Expand Down Expand Up @@ -71,7 +73,7 @@ export function ProjectTabBar({ hideWhenSingle = false }: ProjectTabBarProps) {
)}
aria-hidden="true"
/>
<span className="min-w-0 truncate">{project.name}</span>
<span className="min-w-0 truncate">{displayName}</span>
{project.isActive ? (
<span
aria-hidden="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { convertFileSrc } from "@/platform/tauri-core";
import { useLayoutEffect, useMemo, useState, type ReactNode } from "react";
import { useFileSystemStore } from "@/features/file-system/stores/file-system.store";
import { useRecentFoldersStore } from "@/features/file-system/stores/recent-folders.store";
import { getProjectDisplayLabel } from "@/features/window/utils/project-display-label";
import { useTranslation } from "@/i18n/locale-provider";
import { useWorkspaceTabsStore } from "@/features/window/stores/workspace-tabs.store";
import {
Expand Down Expand Up @@ -114,7 +115,9 @@ export function TitleProjectMenu({ onOpenProjectPicker }: TitleProjectMenuProps)
const handleOpenFolder = useFileSystemStore((state) => state.handleOpenFolder);
const switchToProject = useFileSystemStore((state) => state.switchToProject);
const isSwitchingProject = useFileSystemStore((state) => state.isSwitchingProject);
const projectLabel = activeProject?.name ?? t("projectOpen.title");
const projectLabel = activeProject
? getProjectDisplayLabel(activeProject)
: t("projectOpen.title");
const [isOpen, setIsOpen] = useState(false);
const [menuNode, setMenuNode] = useState<HTMLDivElement | null>(null);
const projects = useMemo(
Expand Down Expand Up @@ -196,7 +199,7 @@ export function TitleProjectMenu({ onOpenProjectPicker }: TitleProjectMenuProps)
{projects.openProjects.map((project) => (
<ProjectMenuRow
key={project.id}
name={project.name}
name={getProjectDisplayLabel(project)}
path={project.path}
iconPath={project.customIcon}
active={project.isActive}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useSettingsStore } from "@/features/settings/stores/settings.store";
import { createTranslator } from "@/i18n/locale";
import { showPromptDialog } from "@/ui/dialog";
import { useProjectStore } from "../stores/project.store";
import type { ProjectTab } from "../stores/workspace-tabs.store";
import { useWorkspaceTabsStore } from "../stores/workspace-tabs.store";
import { getProjectDisplayLabel } from "../utils/project-display-label";

export async function promptProjectDisplayAlias(project: ProjectTab): Promise<void> {
const t = createTranslator(useSettingsStore.getState().settings.displayLanguage);
const result = await showPromptDialog(t("titleProject.displayAliasPrompt"), {
defaultValue: project.displayAlias ?? "",
placeholder: project.path,
title: t("titleProject.setDisplayAlias"),
});

if (result === null) {
return;
}

const trimmedAlias = result.trim();
useWorkspaceTabsStore
.getState()
.actions.setProjectDisplayAlias(
project.id,
trimmedAlias.length > 0 ? trimmedAlias : undefined,
);

const activeProject = useWorkspaceTabsStore.getState().actions.getActiveProjectTab();
if (activeProject?.id !== project.id) {
return;
}

const updatedProject = useWorkspaceTabsStore
.getState()
.projectTabs.find((projectTab) => projectTab.id === project.id);
if (!updatedProject) {
return;
}

useProjectStore.getState().actions.setProjectName(getProjectDisplayLabel(updatedProject));
}
7 changes: 4 additions & 3 deletions windows/tauri/src/features/window/stores/project.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { createWorkspaceScopedStore } from "@/features/workspace/stores/create-w
import { getFolderName } from "@/utils/path-helpers";
import { useWorkspaceTabsStore } from "@/features/window/stores/workspace-tabs.store";
import { createTranslator } from "@/i18n/locale";
import { getProjectDisplayLabel } from "../utils/project-display-label";

const getCurrentTranslator = () =>
createTranslator(useSettingsStore.getState().settings.displayLanguage);
Expand Down Expand Up @@ -37,13 +38,13 @@ const createProjectStore = () =>
? getCurrentTranslator()("projectPicker.remoteProjectName", {
name: connection.name,
})
: activeTab.name;
: getProjectDisplayLabel(activeTab);
} catch {
return activeTab.name;
return getProjectDisplayLabel(activeTab);
}
}

return activeTab.name;
return getProjectDisplayLabel(activeTab);
}

const { rootFolderPath } = get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface ProjectTab {
isActive: boolean;
lastOpened: number;
customIcon?: string;
/** User-defined label shown after the folder name to distinguish same-named projects. */
displayAlias?: string;
theme?: string;
}

Expand All @@ -41,6 +43,7 @@ interface WorkspaceTabsActions {
hasProjectTab: (path: string) => boolean;
renameRemoteProjectTabs: (connectionId: string, connectionName: string) => void;
setProjectIcon: (projectId: string, iconPath: string | undefined) => void;
setProjectDisplayAlias: (projectId: string, displayAlias: string | undefined) => void;
setProjectTheme: (projectId: string, theme: string) => void;
}

Expand Down Expand Up @@ -157,6 +160,18 @@ const useWorkspaceTabsStoreBase = create<WorkspaceTabsStore>()(
});
},

setProjectDisplayAlias: (projectId: string, displayAlias: string | undefined) => {
set((state) => {
const tab = state.projectTabs.find((projectTab) => projectTab.id === projectId);
if (!tab) {
return;
}

const trimmedAlias = displayAlias?.trim();
tab.displayAlias = trimmedAlias && trimmedAlias.length > 0 ? trimmedAlias : undefined;
});
},

setProjectTheme: (projectId: string, theme: string) => {
set((state) => {
const tab = state.projectTabs.find((projectTab) => projectTab.id === projectId);
Expand Down
Loading
Loading