Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/development/platform-parity-matrix.csv
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ agent-codex-subscription,AI,Agent 对话,Codex 供应商选择官方订阅:复
agent-subscription-quota,AI,Agent 对话,Codex 订阅额度:上下文右侧紧凑显示、可见时定时刷新、悬停多窗口与重置时间、未知与过期状态及账号隔离,已实现,待验证,部分实现,待验证,Agent,macOS:订阅会话上下文右侧显示额度,悬停查看窗口时长/已用百分比/重置和更新时间;真实账号验证可见且活跃时每分钟刷新、隐藏不轮询、单连接不并发探测,查询不创建会话或模型请求;周窗口作为 primary 仍显示 7d,缺失不报 0%;网络失败保留并灰显旧值,账号不匹配清除;API Key 模式不查询。窄宽面板、深浅主题、关闭面板/项目和取消时确认探测进程清理。Windows UI 与真实账号验证待完成。,,macos/Sources/Lithe/Views/Agent/AgentSubscriptionQuotaView.swift; macos/Sources/LitheAgentConversationModule/Application/AgentSubscriptionQuota.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift; rust/lithe-agent-host/src/subscription/tests.rs,rust/lithe-agent-host/src/subscription.rs; shared/fixtures/agent/acp-events-v1.json
workspace-open-switch,工作区,工作区生命周期,打开工作区与切换项目,已实现,待验证,已实现,待验证,Workspace,打开多个项目并在项目之间切换,确认当前项目、文件树和编辑器状态正确。,,macos/Sources/Lithe/Views/Workspace; macos/Sources/Lithe/Services/Workspace,windows/tauri/src/features/workspace
workspace-files,工作区,工作区生命周期,文件树与文件操作,已实现,待验证,已实现,待验证,Workspace,新建、移动、重命名、删除文件和目录,并确认相对路径与错误提示一致。,,macos/Sources/Lithe/Views/Workspace,windows/tauri/src/features/file-system
workspace-tree-batch-files,工作区,工作区生命周期,项目树多选、范围选择及批量复制、重复和移到废纸篓,已实现,待验证,未实现,不适用,Workspace,macOS 项目树中使用 ⌘ 点击增减选择、Shift 点击正反向连续选择,Control 点击仍打开右键菜单,右键已选项保持集合;⌘A 全选焦点项的同级项目(点击文件夹时选它旁边的项目而不是其内容,未选中时选项目顶层项目),已选文件夹下展开的内容同时显示为选中;打开文件后单选高亮跟随编辑器;拷贝并粘贴到目标目录、批量创建副本和移到废纸篓,确认同名不覆盖、父子去重、未保存保护及取消删除;确认删除后、废纸篓操作完成前修改的文件不会被删除或关闭,失败时提示剩余项目未移动;点击编辑器或在点击项目树后用快捷键打开全局搜索时,⌘A/⌘C/⌘V 恢复文本操作。Windows 尚无该多选入口。,,macos/Sources/Lithe/Views/Workspace/ProjectSidebarView.swift; macos/Sources/Lithe/Views/Workspace/ProjectTreeSelection.swift; macos/Sources/LitheWorkspaceModule/Application/WorkspaceFeatureModel.swift; macos/Sources/Lithe/Platform/MacOS/UI/ProjectTreeKeyboardCommands.swift; macos/Sources/Lithe/Platform/MacOS/UI/ProjectTreeModifiedClick.swift; macos/Tests/LitheTests/ProjectTreeSelectionTests.swift; macos/Tests/LitheTests/ProjectTreeBatchFileWorkflowTests.swift,windows/tauri/src/features/file-system
workspace-document-sync,工作区,工作区生命周期,脏状态、保存与外部修改,已实现,待验证,已实现,待验证,Workspace,编辑未保存文件、外部修改文件并重启应用,确认冲突、保存和恢复行为。,,macos/Sources/Lithe/Models/Editor; macos/Sources/Lithe/Services/Workspace,windows/tauri/src/features/editor; windows/tauri/src/features/file-system
workspace-tabs-projects,工作区,工作区生命周期,多项目与多标签,已实现,待验证,已实现,待验证,Workspace,同时打开多个项目和文件,确认标签、项目上下文和关闭恢复行为。 Windows 重复打开同一本地目录(含大小写、分隔符和目录链接别名)时,恢复并聚焦已有窗口及项目标签;连续打开只产生一个窗口,关闭或打开失败后可以重试。,,macos/Sources/Lithe/Views/Workspace; macos/Sources/Lithe/Views/Editor,windows/tauri/src/features/workspace; windows/tauri/src/features/tabs; windows/tauri/src-tauri/src/project_windows.rs; windows/tauri/src-tauri/src/project_window_registry.rs; windows/tauri/src/features/window/services/project-window-router.ts
editor-text-tabs,编辑器,文本编辑,文本编辑与标签生命周期,已实现,待验证,已实现,待验证,Editor,打开、编辑、保存、关闭和恢复多个文本文件,确认光标、脏状态和标签状态;右键标签页确认关闭分组排在其他操作之前;在 macOS 打开无扩展名文本文件,确认标签与项目树图标一致;从外部改为二进制后折叠并展开项目树,确认两处图标更新一致。,,macos/Sources/Lithe/Views/Editor; macos/Sources/Lithe/Models/Editor,windows/tauri/src/features/editor; windows/tauri/src/features/tabs; windows/tauri/src/features/tabs/components/tab-context-menu.tsx; windows/tauri/src/features/tabs/components/tab-context-menu.test.ts
Expand Down
9 changes: 5 additions & 4 deletions docs/development/platform-parity-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

- 最后复核:2026-09-29
- 盘点状态:initial-static-inventory(根据 macOS Views/Application/Services、Windows features/extensions 和共享契约的代码入口进行初版盘点;未替代真实运行验收。)
- 功能项:122
- macOS:实现:✅ 107 已实现,🟡 3 部分实现,❌ 7 未实现,🧩 5 平台专属;验证:✔️ 1 已验证,🔍 109 待验证,— 12 不适用
- Windows:实现:✅ 102 已实现,🟡 11 部分实现,❌ 7 未实现,🧩 2 平台专属;验证:✔️ 0 已验证,🔍 115 待验证,— 7 不适用
- 功能项:123
- macOS:实现:✅ 108 已实现,🟡 3 部分实现,❌ 7 未实现,🧩 5 平台专属;验证:✔️ 1 已验证,🔍 110 待验证,— 12 不适用
- Windows:实现:✅ 102 已实现,🟡 11 部分实现,❌ 8 未实现,🧩 2 平台专属;验证:✔️ 0 已验证,🔍 115 待验证,— 8 不适用

## 实现状态定义

Expand Down Expand Up @@ -59,12 +59,13 @@
</details>

<details>
<summary><strong>工作区</strong> · 7 个能力点</summary>
<summary><strong>工作区</strong> · 8 个能力点</summary>

| 功能组 | 能力点 | macOS | Windows | 负责人 | 验证方式 | 备注 |
| --- | --- | --- | --- | --- | --- | --- |
| 工作区生命周期 | **打开工作区与切换项目**<br><sub>workspace-open-switch</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Workspace`、`macos/Sources/Lithe/Services/Workspace`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/workspace`</sub> | Workspace | 打开多个项目并在项目之间切换,确认当前项目、文件树和编辑器状态正确。 | |
| 工作区生命周期 | **文件树与文件操作**<br><sub>workspace-files</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Workspace`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/file-system`</sub> | Workspace | 新建、移动、重命名、删除文件和目录,并确认相对路径与错误提示一致。 | |
| 工作区生命周期 | **项目树多选、范围选择及批量复制、重复和移到废纸篓**<br><sub>workspace-tree-batch-files</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Workspace/ProjectSidebarView.swift`、`macos/Sources/Lithe/Views/Workspace/ProjectTreeSelection.swift`、`macos/Sources/LitheWorkspaceModule/Application/WorkspaceFeatureModel.swift`、`macos/Sources/Lithe/Platform/MacOS/UI/ProjectTreeKeyboardCommands.swift`、`macos/Sources/Lithe/Platform/MacOS/UI/ProjectTreeModifiedClick.swift`、`macos/Tests/LitheTests/ProjectTreeSelectionTests.swift`、`macos/Tests/LitheTests/ProjectTreeBatchFileWorkflowTests.swift`</sub> | ❌ 未实现<br><sub>— 不适用</sub><br><sub>`windows/tauri/src/features/file-system`</sub> | Workspace | macOS 项目树中使用 ⌘ 点击增减选择、Shift 点击正反向连续选择,Control 点击仍打开右键菜单,右键已选项保持集合;⌘A 全选焦点项的同级项目(点击文件夹时选它旁边的项目而不是其内容,未选中时选项目顶层项目),已选文件夹下展开的内容同时显示为选中;打开文件后单选高亮跟随编辑器;拷贝并粘贴到目标目录、批量创建副本和移到废纸篓,确认同名不覆盖、父子去重、未保存保护及取消删除;确认删除后、废纸篓操作完成前修改的文件不会被删除或关闭,失败时提示剩余项目未移动;点击编辑器或在点击项目树后用快捷键打开全局搜索时,⌘A/⌘C/⌘V 恢复文本操作。Windows 尚无该多选入口。 | |
| 工作区生命周期 | **脏状态、保存与外部修改**<br><sub>workspace-document-sync</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Models/Editor`、`macos/Sources/Lithe/Services/Workspace`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/editor`、`windows/tauri/src/features/file-system`</sub> | Workspace | 编辑未保存文件、外部修改文件并重启应用,确认冲突、保存和恢复行为。 | |
| 工作区生命周期 | **多项目与多标签**<br><sub>workspace-tabs-projects</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Workspace`、`macos/Sources/Lithe/Views/Editor`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/workspace`、`windows/tauri/src/features/tabs`、`windows/tauri/src-tauri/src/project_windows.rs`、`windows/tauri/src-tauri/src/project_window_registry.rs`、`windows/tauri/src/features/window/services/project-window-router.ts`</sub> | Workspace | 同时打开多个项目和文件,确认标签、项目上下文和关闭恢复行为。 Windows 重复打开同一本地目录(含大小写、分隔符和目录链接别名)时,恢复并聚焦已有窗口及项目标签;连续打开只产生一个窗口,关闭或打开失败后可以重试。 | |
| 项目浏览 | **项目文件树与资源打开**<br><sub>file-explorer</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`macos/Sources/Lithe/Views/Workspace/ProjectSidebarView.swift`、`macos/Sources/Lithe/Models/Workspace`</sub> | ✅ 已实现<br><sub>🔍 待验证</sub><br><sub>`windows/tauri/src/features/file-explorer`、`windows/tauri/src/features/sidebar`</sub> | Workspace | 浏览目录、展开/折叠、打开资源并在文件变更后刷新树。 | |
Expand Down
8 changes: 8 additions & 0 deletions macos/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,14 @@
"Could not open HTML in browser" = "Could not open HTML in browser";
"Choose a default web browser in System Settings, then retry." = "Choose a default web browser in System Settings, then retry.";

"Paste" = "Paste";
"Copied files" = "Copied files";
"Move %lld items to Trash?" = "Move %lld items to Trash?";
"Pasted files" = "Pasted files";
"Copy Files" = "Copy";
"Choose a directory inside the project" = "Choose a directory inside the project";
"Cannot copy a directory into itself" = "Cannot copy a directory into itself";
"Stopped moving the remaining items to Trash" = "Stopped moving the remaining items to Trash";
"AI tool connections (MCP)" = "AI tool connections (MCP)";
"Authorize this project to expose environment, Maven, run configurations and output to connected agents. Output may contain sensitive project data. Access ends when this project or Lithe closes." = "Authorize this project to expose environment, Maven, run configurations and output to connected agents. Output may contain sensitive project data. Access ends when this project or Lithe closes.";
"Allow project environment and Maven configuration changes" = "Allow project environment and Maven configuration changes";
Expand Down
9 changes: 9 additions & 0 deletions macos/Resources/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2557,6 +2557,15 @@

"Could not open HTML in browser" = "无法在浏览器中打开 HTML";
"Choose a default web browser in System Settings, then retry." = "请在系统设置中选择默认网页浏览器,然后重试。";

"Paste" = "粘贴";
"Copied files" = "已拷贝文件";
"Move %lld items to Trash?" = "将 %lld 个项目移到废纸篓?";
"Pasted files" = "已粘贴文件";
"Copy Files" = "拷贝";
"Choose a directory inside the project" = "请选择项目内的目录";
"Cannot copy a directory into itself" = "不能将目录复制到其自身或子目录中";
"Stopped moving the remaining items to Trash" = "已停止将剩余项目移到废纸篓";
"%llds" = "%lld秒";
"%lldm %llds" = "%lld分 %lld秒";
"%lldh %lldm %llds" = "%lld小时 %lld分 %lld秒";
Expand Down
5 changes: 5 additions & 0 deletions macos/Sources/Lithe/Core/Ports/PlatformUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ protocol PlatformUI: AnyObject {
func open(_ url: URL)
func openHTMLInBrowser(_ url: URL) async throws
func copyToClipboard(_ value: String)
func copyFilesToClipboard(_ urls: [URL]) -> Bool
func fileURLsFromClipboard() -> [URL]
func markdownImageFromClipboard() -> MarkdownImageSource?
func startAccessingProject(_ url: URL) -> Bool
func stopAccessingProject(_ url: URL)
Expand All @@ -21,6 +23,9 @@ extension PlatformUI {
throw CocoaError(.featureUnsupported)
}

func copyFilesToClipboard(_ urls: [URL]) -> Bool { false }
func fileURLsFromClipboard() -> [URL] { [] }

func activateApplication() {}
func startAccessingProject(_ url: URL) -> Bool { false }
func stopAccessingProject(_ url: URL) {}
Expand Down
16 changes: 16 additions & 0 deletions macos/Sources/Lithe/Models/AppModel/AppModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,14 @@ final class AppModel: ObservableObject, Identifiable, UnsavedDocumentHandling {
await workspaceFeature.duplicateProjectItem(at: sourceURL)
}

func duplicateProjectItems(_ urls: [URL]) async {
await workspaceFeature.duplicateProjectItems(urls)
}

func requestDeleteProjectItems(_ urls: [URL]) {
workspaceFeature.requestDeleteProjectItems(urls)
}

func requestDeleteProjectItem(at url: URL, isDirectory: Bool) {
workspaceFeature.requestDeleteProjectItem(at: url, isDirectory: isDirectory)
}
Expand All @@ -1062,6 +1070,14 @@ final class AppModel: ObservableObject, Identifiable, UnsavedDocumentHandling {
platformUI.revealInFileBrowser(url)
}

func copyProjectItems(_ urls: [URL]) {
if platformUI.copyFilesToClipboard(urls) { showNotification("Copied files") }
}

func pasteProjectItems(in directory: URL) async {
await workspaceFeature.pasteProjectItems(platformUI.fileURLsFromClipboard(), in: directory)
}

func copyProjectItemPath(_ url: URL, relative: Bool) {
let relativeValue = relativePath(for: url)
let value = relative ? (relativeValue.isEmpty ? "." : relativeValue) : url.path
Expand Down
18 changes: 18 additions & 0 deletions macos/Sources/Lithe/Platform/MacOS/UI/MacFileClipboard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import AppKit
import Foundation

/// File URLs use the native pasteboard format shared with Finder.
@MainActor
enum MacFileClipboard {
static func write(_ urls: [URL], to pasteboard: NSPasteboard) -> Bool {
guard !urls.isEmpty else { return false }
pasteboard.clearContents()
return pasteboard.writeObjects(urls.map { $0 as NSURL })
}

static func read(from pasteboard: NSPasteboard) -> [URL] {
(pasteboard.readObjects(
forClasses: [NSURL.self], options: [.urlReadingFileURLsOnly: true]
) as? [URL]) ?? []
}
}
8 changes: 8 additions & 0 deletions macos/Sources/Lithe/Platform/MacOS/UI/MacPlatformUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ final class MacPlatformUI: PlatformUI {
NSPasteboard.general.setString(value, forType: .string)
}

func copyFilesToClipboard(_ urls: [URL]) -> Bool {
MacFileClipboard.write(urls, to: .general)
}

func fileURLsFromClipboard() -> [URL] {
MacFileClipboard.read(from: .general)
}

func markdownImageFromClipboard() -> MarkdownImageSource? {
MarkdownClipboardImageReader.read(from: .general)
}
Expand Down
Loading
Loading