Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f521e5f
docs: fix star history sampling
1lck Aug 8, 2026
418e5f3
fix: embed contributor avatars
1lck Aug 8, 2026
2d5bbf8
docs: polish star history chart
1lck Aug 8, 2026
604c479
Move LSP provider catalog into Rust core
xiaoyumuxi Aug 10, 2026
39b8011
Move LSP text edits into Rust core
xiaoyumuxi Aug 10, 2026
853d1b8
Add Rust builtin LSP fallbacks
xiaoyumuxi Aug 10, 2026
1ef59a2
Add Rust LSP client state core
xiaoyumuxi Aug 10, 2026
3de53b7
Move LSP launch metadata into Rust catalog
xiaoyumuxi Aug 10, 2026
d100168
Refine LSP control center interactions
xiaoyumuxi Aug 10, 2026
d2b14d2
Wire Swift LSP runtime to Rust client core
xiaoyumuxi Aug 10, 2026
84622a9
Route LSP feature requests through Rust core
xiaoyumuxi Aug 10, 2026
cdcbe66
Wire LSP rename and formatting through Rust core
xiaoyumuxi Aug 10, 2026
1a6b805
Show LSP server list scrollbar
xiaoyumuxi Aug 10, 2026
5750b24
Route LSP code actions through Rust core
xiaoyumuxi Aug 10, 2026
47b3e3a
Route LSP completion resolve through Rust core
xiaoyumuxi Aug 10, 2026
d362926
Route LSP code action resolve through Rust core
xiaoyumuxi Aug 10, 2026
bb7f24f
Route LSP execute command through Rust core
xiaoyumuxi Aug 10, 2026
59c8acf
Move LSP outbound framing into Rust core
xiaoyumuxi Aug 10, 2026
4749a43
Move LSP inbound framing parser into Rust core
xiaoyumuxi Aug 10, 2026
ea515be
Generalize language tooling and validate gopls
xiaoyumuxi Aug 10, 2026
978e561
Make LSP providers dynamically configurable
xiaoyumuxi Aug 11, 2026
4cdd480
Add configurable LSP tool installation
xiaoyumuxi Aug 11, 2026
2bb72b7
Add LSP setup controls
xiaoyumuxi Aug 11, 2026
eedd014
Add stateful Rust LSP session host
xiaoyumuxi Aug 11, 2026
4bbcdc2
Move stdio LSP sessions onto Rust handles
xiaoyumuxi Aug 11, 2026
0a816fa
Merge branch '1lck:main' into feat/LSP
xiaoyumuxi Aug 11, 2026
30dbc8c
Merge remote-tracking branch 'fork-version/feat/LSP' into feat/LSP
xiaoyumuxi Aug 11, 2026
7ea32bb
Reorganize Rust core into domain packages
xiaoyumuxi Aug 11, 2026
a9e943b
Enforce Rust core package layout
xiaoyumuxi Aug 11, 2026
9783629
Document Rust core package boundaries
xiaoyumuxi Aug 11, 2026
9770a93
Track LSP process state in control center
xiaoyumuxi Aug 11, 2026
a320d6d
Validate LSP executables before launch
xiaoyumuxi Aug 11, 2026
e883ce4
Expose degraded language catalog loads
xiaoyumuxi Aug 11, 2026
c6135e6
Make LSP readiness and failures authoritative
xiaoyumuxi Aug 11, 2026
ce75b14
Distinguish executable verification from LSP readiness
xiaoyumuxi Aug 11, 2026
3d3f786
Preserve LSP diagnostic metadata without false errors
xiaoyumuxi Aug 11, 2026
b499a6b
Own LSP diagnostics by provider lifecycle
xiaoyumuxi Aug 11, 2026
60bf18e
Derive LSP control center from runtime truth
xiaoyumuxi Aug 11, 2026
7fae1da
Stabilize LSP timeout regressions under load
xiaoyumuxi Aug 11, 2026
a1e8acc
fix:clean the arch of the java LSP
xiaoyumuxi Aug 11, 2026
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
20 changes: 6 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

## About Lithe

Lithe is a native macOS IDE built for AI-assisted development. It preserves the project browsing, editing, search, code navigation, Git, run, and debug workflows familiar to IntelliJ IDEA users while starting Java language services, terminals, Maven, and debug processes only when needed.
Lithe is a native macOS IDE built for AI-assisted development. It preserves the project browsing, editing, search, code navigation, Git, run, and debug workflows familiar to IntelliJ IDEA users while starting language servers, terminals, Maven, and debug processes only when needed.

When an external AI tool changes a project, Lithe helps you locate the affected code, run the project, review the diff, and decide which changes to stage, undo, or commit.

Expand All @@ -61,7 +61,7 @@ When an external AI tool changes a project, Lithe helps you locate the affected
2. Maven management, breakpoint debugging, and custom run configurations.
3. Git management and side-by-side diff review.
4. Double-Shift search and `Command + Shift + F` project-wide search.
5. Code navigation and reference lookup.
5. Process-free lightweight completion and current-file navigation, with on-demand language servers for richer completion, hover, and semantic navigation.
6. Local snapshot history.
7. Multiple projects open within the app.
8. Multiple files open independently in the same window.
Expand Down Expand Up @@ -104,7 +104,7 @@ When an external AI tool changes a project, Lithe helps you locate the affected

## Use Lithe

Lithe requires macOS 14 or later. Java project features require a JDK; JDK 17 or JDK 21 is recommended. Semantic navigation requires Eclipse JDT LS. Maven projects need either a project `mvnw` or a system Maven installation.
Lithe requires macOS 14 or later. Java project features require a JDK; JDK 17 or JDK 21 is recommended. Maven projects need either a project `mvnw` or a system Maven installation. Lightweight completion does not start an external process; when a matching language server is installed, Lithe routes the capabilities it actually advertises through the shared Rust LSP Core. See the [language tooling and LSP architecture](./docs/architecture/language-tooling.md) for provider configuration and compatibility details.

Download the latest macOS `.dmg` from [GitHub Releases](https://github.com/1lck/Lithe-IDEA/releases/latest). If a release provides architecture-specific installers, choose `arm64` for Apple silicon or `x86_64` for an Intel Mac. Open the disk image, drag `Lithe.app` into `/Applications`, and launch it.

Expand All @@ -129,12 +129,6 @@ xattr -dr com.apple.quarantine "/Applications/Lithe.app"
open "/Applications/Lithe.app"
```

Install JDT LS with Homebrew:

```bash
brew install jdtls
```

After opening a project, use **Settings → Project** to configure the project JDK, Maven, and the JDK used by Maven. Lithe also detects Java and Maven from common system locations.

## Architecture
Expand Down Expand Up @@ -268,12 +262,10 @@ Lithe is licensed under the [Apache License 2.0](./LICENSE).

## Star History

Each point shows the repository's cumulative Star count at `00:00` Beijing time on that date. The chart starts at zero on August 2, 2026.

<a href="https://www.star-history.com/#1lck/Lithe-IDEA&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/1lck/Lithe-IDEA/chart-assets/star-history-dark.svg" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/1lck/Lithe-IDEA/chart-assets/star-history-light.svg" />
<img alt="Star History Chart" src="https://raw.githubusercontent.com/1lck/Lithe-IDEA/chart-assets/star-history-light.svg" />
</picture>
<img alt="Star History Chart" src="https://raw.githubusercontent.com/1lck/Lithe-IDEA/chart-assets/star-history-light.svg" />
</a>

## Contact us
Expand Down
20 changes: 6 additions & 14 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

## 项目简介

Lithe 是一款面向 AI 辅助开发的原生 macOS IDE。它保留 IntelliJ IDEA 用户熟悉的项目浏览、编辑、搜索、代码导航、Git、运行和调试工作流,同时让 Java 语言服务、终端、Maven 和调试进程只在需要时启动。
Lithe 是一款面向 AI 辅助开发的原生 macOS IDE。它保留 IntelliJ IDEA 用户熟悉的项目浏览、编辑、搜索、代码导航、Git、运行和调试工作流,同时让语言服务器、终端、Maven 和调试进程只在需要时启动。

当外部 AI 工具修改项目后,你可以用 Lithe 定位受影响的代码、运行项目、审查 Diff,并决定暂存、撤销或提交哪些修改。

Expand All @@ -61,7 +61,7 @@ Lithe 是一款面向 AI 辅助开发的原生 macOS IDE。它保留 IntelliJ ID
2. 支持 Maven 管理、断点调试和自定义启动配置。
3. 支持 Git 管理和 Diff 审查。
4. 支持双击 Shift 搜索,以及 `Command + Shift + F` 全局搜索。
5. 支持代码跳转和代码引用查找
5. 支持无需 LSP 的轻量补全和当前文件导航,并可按需使用语言服务器增强补全、悬浮与语义导航
6. 支持本地快照保存。
7. 支持在应用内打开多个项目。
8. 支持在同一窗口打开多个文件,各文件相互独立。
Expand Down Expand Up @@ -104,7 +104,7 @@ Lithe 是一款面向 AI 辅助开发的原生 macOS IDE。它保留 IntelliJ ID

## 如何使用

Lithe 需要 macOS 14 或更高版本。Java 项目功能需要 JDK,推荐使用 JDK 17 或 JDK 21;语义导航需要 Eclipse JDT LS;Maven 项目需要项目自带 `mvnw` 或系统中可用的 Maven。
Lithe 需要 macOS 14 或更高版本。Java 项目功能需要 JDK,推荐使用 JDK 17 或 JDK 21;Maven 项目需要项目自带 `mvnw` 或系统中可用的 Maven。轻量补全无需启动外部进程;安装相应语言服务器后,Lithe 会通过共享 Rust LSP Core 按服务器实际声明的能力提供语义功能。详细设计与自定义 provider 配置见[语言工具与 LSP 架构](./docs/architecture/language-tooling.md)

从 [GitHub Releases](https://github.com/1lck/Lithe-IDEA/releases/latest) 下载最新的 macOS `.dmg`。如果该版本提供独立架构安装包,M 系列芯片选择 `arm64`,Intel 芯片选择 `x86_64`。打开磁盘映像,将 `Lithe.app` 拖入 `/Applications` 后启动。

Expand All @@ -129,12 +129,6 @@ xattr -dr com.apple.quarantine "/Applications/Lithe.app"
open "/Applications/Lithe.app"
```

使用 Homebrew 安装 JDT LS:

```bash
brew install jdtls
```

打开项目后,在 **Settings → Project** 中配置 Project JDK、Maven 和 Maven 使用的 JDK。Lithe 也会从常见的系统位置自动探测 Java 与 Maven。

## 架构图
Expand Down Expand Up @@ -268,12 +262,10 @@ Lithe 采用 [Apache License 2.0](./LICENSE) 授权。

## Star History

每个日期点表示北京时间当天 `00:00` 时仓库的累计 Star 数。图表从 2026 年 8 月 2 日的 0 开始。

<a href="https://www.star-history.com/#1lck/Lithe-IDEA&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/1lck/Lithe-IDEA/chart-assets/star-history-dark.svg" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/1lck/Lithe-IDEA/chart-assets/star-history-light.svg" />
<img alt="Star History 图表" src="https://raw.githubusercontent.com/1lck/Lithe-IDEA/chart-assets/star-history-light.svg" />
</picture>
<img alt="Star History 图表" src="https://raw.githubusercontent.com/1lck/Lithe-IDEA/chart-assets/star-history-light.svg" />
</a>

## 联系我们
Expand Down
29 changes: 15 additions & 14 deletions Sources/Lithe/Application/AppServices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ final class AppServices {
/// registries remain exposed below for source compatibility with existing
/// feature models while new composition should use this value.
let languagePacks: LanguagePackRegistry
let languageProviderCatalogSource: any LanguageProviderCatalogSource
/// Initial catalog load outcome, including whether startup fell back to a
/// compatibility catalog or rejected a workspace override.
let languageProviderCatalogSnapshot: LanguageProviderCatalogSnapshot
/// Metadata-only provider catalog; providers are activated on demand.
let languageProviderCatalog: LanguageProviderCatalog
let runToolchainRegistry: RunToolchainRegistry
let languageToolingSessions: LanguageToolingSessionManager
let languageServerTools: LanguageServerToolService
let debugLaunchConfigurationResolver: DebugLaunchConfigurationResolver
let languageTestService: LanguageTestService
let workspaceOperations: any WorkspaceOperations
Expand All @@ -31,8 +36,6 @@ final class AppServices {
let fileStorage: any FileStorage
let fileOperations: any WorkspaceFileOperations
let projectRuntimeService: ProjectRuntimeService
let javaLanguageService: JavaLanguageService
let javaImplementationMarkerService: JavaImplementationMarkerService
let mavenService: MavenService
let runService: RunService
let javaDebugService: JavaDebugService
Expand All @@ -51,10 +54,12 @@ final class AppServices {
let shortcutDetectorFactory: any ShortcutDetectorFactory

init(
languageProviderCatalog: LanguageProviderCatalog = .standard,
languageProviderCatalogSource: any LanguageProviderCatalogSource,
languageProviderCatalogSnapshot: LanguageProviderCatalogSnapshot? = nil,
languagePacks: LanguagePackRegistry? = nil,
runToolchainRegistry: RunToolchainRegistry? = nil,
languageToolingSessions: LanguageToolingSessionManager? = nil,
languageServerTools: LanguageServerToolService,
debugLaunchConfigurationResolver: DebugLaunchConfigurationResolver? = nil,
languageTestService: LanguageTestService,
workspaceOperations: any WorkspaceOperations,
Expand All @@ -66,8 +71,6 @@ final class AppServices {
fileStorage: any FileStorage,
fileOperations: any WorkspaceFileOperations,
projectRuntimeService: ProjectRuntimeService,
javaLanguageService: JavaLanguageService,
javaImplementationMarkerService: JavaImplementationMarkerService,
mavenService: MavenService,
runService: RunService,
javaDebugService: JavaDebugService,
Expand All @@ -85,21 +88,21 @@ final class AppServices {
platformUI: any PlatformUI,
shortcutDetectorFactory: any ShortcutDetectorFactory
) {
self.languageProviderCatalogSource = languageProviderCatalogSource
let resolvedCatalogSnapshot = languageProviderCatalogSnapshot
?? languageProviderCatalogSource.load(workspaceURL: nil)
self.languageProviderCatalogSnapshot = resolvedCatalogSnapshot
let resolvedCatalog = resolvedCatalogSnapshot.catalog
let resolvedLanguagePacks = languagePacks ?? LanguagePackRegistry.standard(
catalog: languageProviderCatalog,
runtimes: [
JavaLanguageProviderRuntime(
service: javaLanguageService,
catalog: languageProviderCatalog
)
]
catalog: resolvedCatalog
)
self.languagePacks = resolvedLanguagePacks
self.languageProviderCatalog = resolvedLanguagePacks.catalog
self.runToolchainRegistry = runToolchainRegistry ?? resolvedLanguagePacks.toolchainRegistry
self.languageToolingSessions = languageToolingSessions ?? LanguageToolingSessionManager(
registry: resolvedLanguagePacks
)
self.languageServerTools = languageServerTools
self.debugLaunchConfigurationResolver = debugLaunchConfigurationResolver
?? DebugLaunchConfigurationResolver(fileStorage: fileStorage)
self.languageTestService = languageTestService
Expand All @@ -112,8 +115,6 @@ final class AppServices {
self.fileStorage = fileStorage
self.fileOperations = fileOperations
self.projectRuntimeService = projectRuntimeService
self.javaLanguageService = javaLanguageService
self.javaImplementationMarkerService = javaImplementationMarkerService
self.mavenService = mavenService
self.runService = runService
self.javaDebugService = javaDebugService
Expand Down
97 changes: 13 additions & 84 deletions Sources/Lithe/Application/JavaFeatureModel.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import Combine
import Foundation

/// Owns Java-only code vision, inlay hints, Maven integration, and legacy Java
/// debug behavior. Shared LSP navigation and editing live in the generic
/// language-tooling pipeline.
/// Owns Java-only code vision, fallback inlay hints, Maven integration, and
/// legacy Java debug behavior. Java LSP navigation and editing are delegated
/// to the Rust host.
@MainActor
final class JavaFeatureModel: ObservableObject {
@Published private(set) var javaDiagnostics: [URL: [JavaDiagnostic]] = [:]
@Published private(set) var javaCodeVisionHints: [URL: [JavaCodeVisionHint]] = [:]
@Published private(set) var javaInlayHints: [URL: [JavaInlayHint]] = [:]

private let service: JavaLanguageService
private let markerService: JavaImplementationMarkerService
private let operations: any JavaMavenOperations
private let workspaceOperations: any WorkspaceOperations
private var documentProvider: (@MainActor () -> EditorDocument?)?
Expand All @@ -23,18 +20,11 @@ final class JavaFeatureModel: ObservableObject {
private var debugFeature: JavaDebugFeatureModel?

init(
service: JavaLanguageService,
markerService: JavaImplementationMarkerService,
operations: any JavaMavenOperations,
workspaceOperations: any WorkspaceOperations
) {
self.service = service
self.markerService = markerService
self.operations = operations
self.workspaceOperations = workspaceOperations
service.onDiagnostics = { [weak self] fileURL, diagnostics in
self?.javaDiagnostics[fileURL.standardizedFileURL] = diagnostics
}
}

func configure(
Expand All @@ -57,8 +47,6 @@ final class JavaFeatureModel: ObservableObject {
self.debugFeature = debugFeature
}

var statusMessage: String { service.statusMessage }

/// Explicit boundary for Java-only editor adornments and legacy services.
/// Callers can avoid scheduling Java work for every supported language.
func handles(fileURL: URL) -> Bool {
Expand All @@ -69,15 +57,9 @@ final class JavaFeatureModel: ObservableObject {
handles(fileURL: fileURL)
}

func configureProjectRoot(_ url: URL) {
service.configureProjectRoot(url)
}

func stop() {
inlayHintTasks.values.forEach { $0.cancel() }
inlayHintTasks.removeAll()
service.stop()
javaDiagnostics = [:]
javaCodeVisionHints = [:]
javaInlayHints = [:]
}
Expand Down Expand Up @@ -163,10 +145,8 @@ final class JavaFeatureModel: ObservableObject {
}

func close(_ document: EditorDocument) {
javaDiagnostics[document.url.standardizedFileURL] = nil
javaCodeVisionHints[document.url.standardizedFileURL] = nil
javaInlayHints[document.url.standardizedFileURL] = nil
markerService.invalidate(document)
}

func refreshCodeVision(
Expand All @@ -184,10 +164,9 @@ final class JavaFeatureModel: ObservableObject {
blameLines: blame
)
let candidates = structure(source: document.text)?.implementationMarkers ?? []
let markers = await implementationMarkers(for: document, candidates: candidates)
let implementationCounts = Dictionary(
uniqueKeysWithValues: markers.map { ($0.line, $0.implementationCount) }
)
let implementationCounts = candidates.reduce(into: [Int: Int]()) { counts, marker in
counts[marker.line] = max(counts[marker.line] ?? 0, marker.implementationCount)
}
guard documentProvider?()?.id == document.id else { return }
javaCodeVisionHints[normalizedURL] = baseHints.map { hint in
JavaCodeVisionHint(
Expand All @@ -214,8 +193,7 @@ final class JavaFeatureModel: ObservableObject {
await self.requestInlayHints(
for: document,
projectFiles: projectFiles,
workspaceRoot: workspaceRoot,
attempt: 0
workspaceRoot: workspaceRoot
)
self.inlayHintTasks[document.id] = nil
}
Expand All @@ -224,50 +202,15 @@ final class JavaFeatureModel: ObservableObject {
private func requestInlayHints(
for document: EditorDocument,
projectFiles: [URL],
workspaceRoot: URL?,
attempt: Int
workspaceRoot: URL?
) async {
guard !Task.isCancelled,
documentProvider?()?.id == document.id else { return }
await withCheckedContinuation { continuation in
inlayHints(for: document) { [weak self] result in
guard let self else {
continuation.resume()
return
}
if case .success(let hints) = result {
self.javaInlayHints[document.url.standardizedFileURL] = hints
Task { @MainActor [weak self, weak document] in
guard let self, let document else {
continuation.resume()
return
}
if hints.isEmpty, attempt < 3 {
try? await Task.sleep(for: .milliseconds(900 * (attempt + 1)))
guard !Task.isCancelled else {
continuation.resume()
return
}
await self.requestInlayHints(
for: document,
projectFiles: projectFiles,
workspaceRoot: workspaceRoot,
attempt: attempt + 1
)
} else if hints.isEmpty {
await self.applyInlayFallback(
for: document,
projectFiles: projectFiles,
workspaceRoot: workspaceRoot
)
}
continuation.resume()
}
} else {
continuation.resume()
}
}
}
await applyInlayFallback(
for: document,
projectFiles: projectFiles,
workspaceRoot: workspaceRoot
)
}

private func applyInlayFallback(
Expand Down Expand Up @@ -302,13 +245,6 @@ final class JavaFeatureModel: ObservableObject {
return String(path.dropFirst(rootPath.count + 1))
}

func implementationMarkers(
for document: EditorDocument,
candidates: [JavaImplementationMarker]
) async -> [JavaImplementationMarker] {
await markerService.markers(for: document, candidates: candidates)
}

func structure(source: String, declarationSources: [String] = []) -> JavaStructureResult? {
operations.structure(source: source, declarationSources: declarationSources)
}
Expand All @@ -328,11 +264,4 @@ final class JavaFeatureModel: ObservableObject {
)
}

func inlayHints(
for document: EditorDocument,
completion: @escaping (Result<[JavaInlayHint], Error>) -> Void
) {
service.inlayHints(document: document, completion: completion)
}

}
Loading
Loading