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
127 changes: 98 additions & 29 deletions packages/app/src/components/session/session-header.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { AppIcon } from "@opencode-ai/ui/app-icon"
import { Button } from "@opencode-ai/ui/button"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Keybind } from "@opencode-ai/ui/keybind"
import { Popover } from "@opencode-ai/ui/popover"
import { Spinner } from "@opencode-ai/ui/spinner"
import { TextField } from "@opencode-ai/ui/text-field"
import { showToast } from "@opencode-ai/ui/toast"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { getFilename } from "@opencode-ai/util/path"
import { useParams } from "@solidjs/router"
import { createEffect, createMemo, For, onCleanup, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { Portal } from "solid-js/web"
import { useParams } from "@solidjs/router"
import { useLayout } from "@/context/layout"
import { useCommand } from "@/context/command"
import { useGlobalSDK } from "@/context/global-sdk"
import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout"
import { usePlatform } from "@/context/platform"
import { useServer } from "@/context/server"
import { useSync } from "@/context/sync"
import { useGlobalSDK } from "@/context/global-sdk"
import { getFilename } from "@opencode-ai/util/path"
import { decode64 } from "@/utils/base64"
import { Persist, persisted } from "@/utils/persist"

import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Button } from "@opencode-ai/ui/button"
import { AppIcon } from "@opencode-ai/ui/app-icon"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { Popover } from "@opencode-ai/ui/popover"
import { TextField } from "@opencode-ai/ui/text-field"
import { Keybind } from "@opencode-ai/ui/keybind"
import { showToast } from "@opencode-ai/ui/toast"
import { StatusPopover } from "../status-popover"

const OPEN_APPS = [
Expand All @@ -45,32 +45,67 @@ type OpenApp = (typeof OPEN_APPS)[number]
type OS = "macos" | "windows" | "linux" | "unknown"

const MAC_APPS = [
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "Visual Studio Code" },
{
id: "vscode",
label: "VS Code",
icon: "vscode",
openWith: "Visual Studio Code",
},
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "Cursor" },
{ id: "zed", label: "Zed", icon: "zed", openWith: "Zed" },
{ id: "textmate", label: "TextMate", icon: "textmate", openWith: "TextMate" },
{ id: "antigravity", label: "Antigravity", icon: "antigravity", openWith: "Antigravity" },
{
id: "antigravity",
label: "Antigravity",
icon: "antigravity",
openWith: "Antigravity",
},
{ id: "terminal", label: "Terminal", icon: "terminal", openWith: "Terminal" },
{ id: "iterm2", label: "iTerm2", icon: "iterm2", openWith: "iTerm" },
{ id: "ghostty", label: "Ghostty", icon: "ghostty", openWith: "Ghostty" },
{ id: "xcode", label: "Xcode", icon: "xcode", openWith: "Xcode" },
{ id: "android-studio", label: "Android Studio", icon: "android-studio", openWith: "Android Studio" },
{ id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" },
{
id: "android-studio",
label: "Android Studio",
icon: "android-studio",
openWith: "Android Studio",
},
{
id: "sublime-text",
label: "Sublime Text",
icon: "sublime-text",
openWith: "Sublime Text",
},
] as const

const WINDOWS_APPS = [
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
{ id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
{ id: "powershell", label: "PowerShell", icon: "powershell", openWith: "powershell" },
{ id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" },
{
id: "powershell",
label: "PowerShell",
icon: "powershell",
openWith: "powershell",
},
{
id: "sublime-text",
label: "Sublime Text",
icon: "sublime-text",
openWith: "Sublime Text",
},
] as const

const LINUX_APPS = [
{ id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" },
{ id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" },
{ id: "zed", label: "Zed", icon: "zed", openWith: "zed" },
{ id: "sublime-text", label: "Sublime Text", icon: "sublime-text", openWith: "Sublime Text" },
{
id: "sublime-text",
label: "Sublime Text",
icon: "sublime-text",
openWith: "Sublime Text",
},
] as const

type OpenOption = (typeof MAC_APPS)[number] | (typeof WINDOWS_APPS)[number] | (typeof LINUX_APPS)[number]
Expand Down Expand Up @@ -213,7 +248,9 @@ export function SessionHeader() {
const view = createMemo(() => layout.view(sessionKey))
const os = createMemo(() => detectOS(platform))

const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({ finder: true })
const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({
finder: true,
})

const apps = createMemo(() => {
if (os() === "macos") return MAC_APPS
Expand Down Expand Up @@ -259,18 +296,34 @@ export function SessionHeader() {

const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
const [menu, setMenu] = createStore({ open: false })
const [openRequest, setOpenRequest] = createStore({
app: undefined as OpenApp | undefined,
})

const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
const current = createMemo(() => options().find((o) => o.id === prefs.app) ?? options()[0])
const opening = createMemo(() => openRequest.app !== undefined)

createEffect(() => {
const value = prefs.app
if (options().some((o) => o.id === value)) return
setPrefs("app", options()[0]?.id ?? "finder")
})
Comment on lines +307 to +311
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gonna leave this for now but for future reference i'd handle something like this at read-time rather than writing to a signal in an effect

const [_prefs, setPrefs] = persisted(...);
const prefs = mergeProps(_prefs, {
  get app() {
    if (options().some((o) => o.id === _prefs.app)) return _prefs.app;
    return options()[0]?.id ?? "finder"
  }
});


const openDir = (app: OpenApp) => {
if (opening() || !canOpen() || !platform.openPath) return
const directory = projectDirectory()
if (!directory) return
if (!canOpen()) return

const item = options().find((o) => o.id === app)
const openWith = item && "openWith" in item ? item.openWith : undefined
Promise.resolve(platform.openPath?.(directory, openWith)).catch((err: unknown) => showRequestError(language, err))
setOpenRequest("app", app)
platform
.openPath(directory, openWith)
.catch((err: unknown) => showRequestError(language, err))
.finally(() => {
setOpenRequest("app", undefined)
})
}

const copyPath = () => {
Expand Down Expand Up @@ -315,7 +368,9 @@ export function SessionHeader() {
<div class="flex min-w-0 flex-1 items-center gap-1.5 overflow-visible">
<Icon name="magnifying-glass" size="small" class="icon-base shrink-0 size-4" />
<span class="flex-1 min-w-0 text-12-regular text-text-weak truncate text-left">
{language.t("session.header.search.placeholder", { project: name() })}
{language.t("session.header.search.placeholder", {
project: name(),
})}
</span>
</div>

Expand Down Expand Up @@ -357,12 +412,21 @@ export function SessionHeader() {
<div class="flex h-[24px] box-border items-center rounded-md border border-border-weak-base bg-surface-panel overflow-hidden">
<Button
variant="ghost"
class="rounded-none h-full py-0 pr-3 pl-0.5 gap-1.5 border-none shadow-none"
class="rounded-none h-full py-0 pr-3 pl-0.5 gap-1.5 border-none shadow-none disabled:!cursor-default"
classList={{
"bg-surface-raised-base-active": opening(),
}}
onClick={() => openDir(current().id)}
disabled={opening()}
aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
>
<div class="flex size-5 shrink-0 items-center justify-center">
<AppIcon id={current().icon} class="size-4" />
<Show
when={opening()}
fallback={<AppIcon id={current().icon} class={openIconSize(current().icon)} />}
>
<Spinner class="size-3.5 text-icon-base" />
</Show>
</div>
<span class="text-12-regular text-text-strong">Open</span>
</Button>
Expand All @@ -377,7 +441,11 @@ export function SessionHeader() {
as={IconButton}
icon="chevron-down"
variant="ghost"
class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-hover"
disabled={opening()}
class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active disabled:!cursor-default"
classList={{
"bg-surface-raised-base-active": opening(),
}}
aria-label={language.t("session.header.open.menu")}
/>
<DropdownMenu.Portal>
Expand All @@ -395,6 +463,7 @@ export function SessionHeader() {
{(o) => (
<DropdownMenu.RadioItem
value={o.id}
disabled={opening()}
onSelect={() => {
setMenu("open", false)
openDir(o.id)
Expand Down
5 changes: 3 additions & 2 deletions packages/desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ tokio-stream = { version = "0.1.18", features = ["sync"] }
process-wrap = { version = "9.0.3", features = ["tokio1"] }

[target.'cfg(windows)'.dependencies]
windows = { version = "0.62", features = ["Win32_System_Threading"] }
windows-sys = { version = "0.61", features = ["Win32_System_Threading", "Win32_System_Registry"] }
windows-core = "0.62"

[target.'cfg(target_os = "linux")'.dependencies]
gtk = "0.18.2"
Expand Down
4 changes: 2 additions & 2 deletions packages/desktop/src-tauri/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use tokio::{
use tokio_stream::wrappers::ReceiverStream;
use tracing::Instrument;
#[cfg(windows)]
use windows::Win32::System::Threading::{CREATE_NO_WINDOW, CREATE_SUSPENDED};
use windows_sys::Win32::System::Threading::{CREATE_NO_WINDOW, CREATE_SUSPENDED};

use crate::server::get_wsl_config;

Expand All @@ -32,7 +32,7 @@ struct WinCreationFlags;
#[cfg(windows)]
impl CommandWrapper for WinCreationFlags {
fn pre_spawn(&mut self, command: &mut Command, _core: &CommandWrap) -> std::io::Result<()> {
command.creation_flags((CREATE_NO_WINDOW | CREATE_SUSPENDED).0);
command.creation_flags(CREATE_NO_WINDOW | CREATE_SUSPENDED);
Ok(())
}
}
Expand Down
Loading
Loading