дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
fix(project): keep attachments in the current project (#2320)
Attach adds a trusted folder to the current project without reopening it or moving its root. Attachments live in the primary project's private .openidea/workspace.json, and detaching the last one restores a plain project. ProjectInfo.name stays the primary project's name; displayName supplies window titles, project tabs and Recent Projects. Attach is no longer a remembered opening policy, and File > Attach Project is available when the policy is not Ask. Attached folders must be trusted, and Restricted Mode names any attached folder that blocks trust. Explicit workspaces keep their behaviour and still cannot be created in or inside a linked project.
Цей коміт міститься в:
1 джерело
023c3f36c2
коміт
1ae8572021
60 files changed
+1155
-233
No files matched your search
@@ -85,7 +85,7 @@ pub async fn open_project(window: Window, path: PathBuf) -> IdeResult<ProjectInf
|
||||
.await
|
||||
.map_err(|e| IdeError::internal(e.to_string()))??;
|
||||
let info = project_info(&project);
|
||||
let _ = window.set_title(&info.name);
|
||||
let _ = window.set_title(&info.display_name);
|
||||
window.state::<AppState>().sink.emit(ide_core::IdeEvent::WindowGroupsChanged);
|
||||
// Every project window is part of the session reopened at the next start.
|
||||
crate::window_groups::persist(window.app_handle()).await?;
|
||||
@@ -121,14 +121,19 @@ fn project_info(project: &crate::state::Project) -> ProjectInfo {
|
||||
pub fn project_rename(window: Window, state: State<'_, AppState>, name: String) -> IdeResult<ProjectInfo> {
|
||||
let project = state.project(window.label())?;
|
||||
let name = name.trim();
|
||||
if let Some(workspace) = ide_workspace::workspace::Workspace::load(&project.root)? {
|
||||
return ide_host::ops::project::save_workspace(&state.host, window.label(), name, workspace.projects);
|
||||
if let Some(workspace) = ide_workspace::workspace::Workspace::load(&project.root)?
|
||||
&& workspace.is_container(&project.root)
|
||||
{
|
||||
ide_host::ops::project::save_workspace(&state.host, window.label(), name, workspace.projects)?;
|
||||
} else {
|
||||
let value = if name.is_empty() { serde_json::Value::Null } else { serde_json::Value::String(name.to_owned()) };
|
||||
project.settings.set(SettingsScope::Project, "general.projectName", value)?;
|
||||
}
|
||||
let value = if name.is_empty() { serde_json::Value::Null } else { serde_json::Value::String(name.to_owned()) };
|
||||
project.settings.set(SettingsScope::Project, "general.projectName", value)?;
|
||||
state.recent.rename(&project.root, (!name.is_empty()).then_some(name))?;
|
||||
crate::jump_list::refresh(&state.app, state.recent.list());
|
||||
Ok(project_info(&project))
|
||||
let info = project_info(&project);
|
||||
state.record_project(&project)?;
|
||||
let _ = window.set_title(&info.display_name);
|
||||
state.sink.emit(ide_core::IdeEvent::WindowGroupsChanged);
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
// Not on the main thread: closing stops the project's language servers, runs and file watcher.
|
||||
|
||||
@@ -42,7 +42,10 @@ pub async fn workspace_update(window: WebviewWindow, name: String, projects: Vec
|
||||
})
|
||||
.await
|
||||
.map_err(|e| IdeError::internal(e.to_string()))??;
|
||||
let _ = window.set_title(&info.name);
|
||||
let state = window.state::<AppState>();
|
||||
let project = state.project(window.label())?;
|
||||
state.record_project(&project)?;
|
||||
let _ = window.set_title(&info.display_name);
|
||||
window.state::<AppState>().sink.emit(ide_core::IdeEvent::WindowGroupsChanged);
|
||||
Ok(info)
|
||||
}
|
||||
@@ -52,7 +55,14 @@ pub async fn workspace_update(window: WebviewWindow, name: String, projects: Vec
|
||||
pub async fn workspace_attach(window: WebviewWindow, path: PathBuf) -> IdeResult<ide_core::IpcPath> {
|
||||
let host = window.state::<AppState>().host.clone();
|
||||
let label = window.label().to_owned();
|
||||
tauri::async_runtime::spawn_blocking(move || ide_host::ops::project::attach_workspace(&host, &label, &path))
|
||||
.await
|
||||
.map_err(|e| IdeError::internal(e.to_string()))?
|
||||
let root =
|
||||
tauri::async_runtime::spawn_blocking(move || ide_host::ops::project::attach_workspace(&host, &label, &path))
|
||||
.await
|
||||
.map_err(|e| IdeError::internal(e.to_string()))??;
|
||||
let state = window.state::<AppState>();
|
||||
let project = state.project(window.label())?;
|
||||
state.record_project(&project)?;
|
||||
let _ = window.set_title(&ide_host::ops::project::info(&project).display_name);
|
||||
state.sink.emit(ide_core::IdeEvent::WindowGroupsChanged);
|
||||
Ok(root)
|
||||
}
|
||||
@@ -224,6 +224,7 @@ pub fn spec(os: Os) -> Vec<Entry> {
|
||||
act("file.new", "New…"),
|
||||
act("file.newScratchFile", "New Scratch File…"),
|
||||
act("file.openProject", "Open…"),
|
||||
act("project.attach", "Attach Project…"),
|
||||
act("file.openFile", "Open File…"),
|
||||
act("file.openInNewWindow", "Open in New Window…"),
|
||||
sub(
|
||||
|
||||
@@ -528,7 +528,7 @@ pub async fn remote_connect(
|
||||
peer.request("invoke", call, None).await.map_err(ide_host::rpc::error_from_reply)?;
|
||||
// As the server resolved it (symlinks, a trailing `/`): the frontend compares it with the project's.
|
||||
let root = serde_json::to_value(&project.root).ok().and_then(|v| v.as_str().map(str::to_owned)).unwrap_or(root);
|
||||
let _ = window.set_title(&format!("{} [{title_tag}]", project.name));
|
||||
let _ = window.set_title(&format!("{} [{title_tag}]", project.display_name));
|
||||
let connection = Arc::new(Connection {
|
||||
peer,
|
||||
target: target.clone(),
|
||||
|
||||
@@ -144,16 +144,24 @@ impl AppState {
|
||||
// A local project replaces a remote one in this window.
|
||||
self.remote_dev.disconnect(window);
|
||||
let project = self.host.open_project(window, root)?;
|
||||
let branch = project.repos.read().primary().and_then(|r| r.branch_info().ok()).and_then(|b| b.head);
|
||||
self.recent.touch(&project.root, branch)?;
|
||||
let display_name = project.settings.effective().general.project_name;
|
||||
if !display_name.trim().is_empty() {
|
||||
self.recent.rename(&project.root, Some(display_name.trim()))?;
|
||||
}
|
||||
crate::jump_list::refresh(&self.app, self.recent.list());
|
||||
self.record_project(&project)?;
|
||||
Ok(project)
|
||||
}
|
||||
|
||||
pub fn record_project(&self, project: &Project) -> IdeResult<()> {
|
||||
let info = ide_host::ops::project::info(project);
|
||||
let attached = info.workspace.as_ref().is_some_and(|w| !w.is_container(&project.root));
|
||||
let branch = if attached {
|
||||
None
|
||||
} else {
|
||||
project.repos.read().primary().and_then(|r| r.branch_info().ok()).and_then(|b| b.head)
|
||||
};
|
||||
self.recent.touch(&project.root, branch)?;
|
||||
self.recent.rename(&project.root, Some(&info.display_name))?;
|
||||
crate::jump_list::refresh(&self.app, self.recent.list());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Opens `window` for editing the loose file `file` (LightEdit); not a recent project.
|
||||
pub fn open_light(&self, window: &str, file: &Path) -> IdeResult<Arc<Project>> {
|
||||
self.remote_dev.disconnect(window);
|
||||
|
||||
@@ -37,7 +37,7 @@ pub fn current(app: &AppHandle, caller: &str) -> Vec<WindowTab> {
|
||||
let project = host.project(label).ok();
|
||||
let name = project
|
||||
.as_ref()
|
||||
.map(|p| ide_host::ops::project::info(p).name)
|
||||
.map(|p| ide_host::ops::project::info(p).display_name)
|
||||
.unwrap_or_else(|| window.title().unwrap_or_default());
|
||||
Some(WindowTab {
|
||||
label: label.clone(),
|
||||
|
||||
Reference in new issue
Заблокувати користувача