Render pending language server work in status bar
This commit is contained in:
parent
4bbf5ed0b9
commit
4243f0c339
4 changed files with 151 additions and 54 deletions
|
@ -51,6 +51,8 @@ pub struct Project {
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
language_servers: HashMap<(WorktreeId, Arc<str>), Arc<LanguageServer>>,
|
language_servers: HashMap<(WorktreeId, Arc<str>), Arc<LanguageServer>>,
|
||||||
started_language_servers: HashMap<(WorktreeId, Arc<str>), Task<Option<Arc<LanguageServer>>>>,
|
started_language_servers: HashMap<(WorktreeId, Arc<str>), Task<Option<Arc<LanguageServer>>>>,
|
||||||
|
pending_language_server_work: HashMap<(usize, String), LspWorkProgress>,
|
||||||
|
next_language_server_id: usize,
|
||||||
client: Arc<client::Client>,
|
client: Arc<client::Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
|
@ -120,8 +122,7 @@ enum LspEvent {
|
||||||
},
|
},
|
||||||
WorkProgress {
|
WorkProgress {
|
||||||
token: String,
|
token: String,
|
||||||
message: Option<String>,
|
progress: LspWorkProgress,
|
||||||
percentage: Option<usize>,
|
|
||||||
},
|
},
|
||||||
WorkEnd {
|
WorkEnd {
|
||||||
token: String,
|
token: String,
|
||||||
|
@ -129,6 +130,12 @@ enum LspEvent {
|
||||||
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct LspWorkProgress {
|
||||||
|
pub message: Option<String>,
|
||||||
|
pub percentage: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ProjectPath {
|
pub struct ProjectPath {
|
||||||
pub worktree_id: WorktreeId,
|
pub worktree_id: WorktreeId,
|
||||||
|
@ -317,6 +324,8 @@ impl Project {
|
||||||
language_servers_with_diagnostics_running: 0,
|
language_servers_with_diagnostics_running: 0,
|
||||||
language_servers: Default::default(),
|
language_servers: Default::default(),
|
||||||
started_language_servers: Default::default(),
|
started_language_servers: Default::default(),
|
||||||
|
pending_language_server_work: Default::default(),
|
||||||
|
next_language_server_id: 0,
|
||||||
nonce: StdRng::from_entropy().gen(),
|
nonce: StdRng::from_entropy().gen(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -386,6 +395,8 @@ impl Project {
|
||||||
language_servers_with_diagnostics_running: 0,
|
language_servers_with_diagnostics_running: 0,
|
||||||
language_servers: Default::default(),
|
language_servers: Default::default(),
|
||||||
started_language_servers: Default::default(),
|
started_language_servers: Default::default(),
|
||||||
|
pending_language_server_work: Default::default(),
|
||||||
|
next_language_server_id: 0,
|
||||||
opened_buffers: Default::default(),
|
opened_buffers: Default::default(),
|
||||||
buffer_snapshots: Default::default(),
|
buffer_snapshots: Default::default(),
|
||||||
nonce: StdRng::from_entropy().gen(),
|
nonce: StdRng::from_entropy().gen(),
|
||||||
|
@ -1172,6 +1183,7 @@ impl Project {
|
||||||
self.started_language_servers
|
self.started_language_servers
|
||||||
.entry(key.clone())
|
.entry(key.clone())
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
|
let server_id = post_inc(&mut self.next_language_server_id);
|
||||||
let language_server = self.languages.start_language_server(
|
let language_server = self.languages.start_language_server(
|
||||||
language.clone(),
|
language.clone(),
|
||||||
worktree_path,
|
worktree_path,
|
||||||
|
@ -1213,8 +1225,12 @@ impl Project {
|
||||||
lsp_events_tx
|
lsp_events_tx
|
||||||
.try_send(LspEvent::WorkProgress {
|
.try_send(LspEvent::WorkProgress {
|
||||||
token,
|
token,
|
||||||
message: report.message,
|
progress: LspWorkProgress {
|
||||||
percentage: report.percentage.map(|p| p as usize),
|
message: report.message,
|
||||||
|
percentage: report
|
||||||
|
.percentage
|
||||||
|
.map(|p| p as usize),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
@ -1233,7 +1249,7 @@ impl Project {
|
||||||
while let Ok(event) = lsp_events_rx.recv().await {
|
while let Ok(event) = lsp_events_rx.recv().await {
|
||||||
let this = this.upgrade(&cx)?;
|
let this = this.upgrade(&cx)?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.on_local_lsp_event(event, &language, cx)
|
this.on_local_lsp_event(server_id, event, &language, cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Some(())
|
Some(())
|
||||||
|
@ -1309,6 +1325,7 @@ impl Project {
|
||||||
|
|
||||||
fn on_local_lsp_event(
|
fn on_local_lsp_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
language_server_id: usize,
|
||||||
event: LspEvent,
|
event: LspEvent,
|
||||||
language: &Arc<Language>,
|
language: &Arc<Language>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
|
@ -1318,43 +1335,53 @@ impl Project {
|
||||||
LspEvent::WorkStart { token } => {
|
LspEvent::WorkStart { token } => {
|
||||||
if Some(&token) == disk_diagnostics_token {
|
if Some(&token) == disk_diagnostics_token {
|
||||||
self.disk_based_diagnostics_started(cx);
|
self.disk_based_diagnostics_started(cx);
|
||||||
self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(
|
self.broadcast_lsp_event(
|
||||||
proto::LspDiskBasedDiagnosticsUpdating {},
|
language_server_id,
|
||||||
));
|
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(
|
||||||
|
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
self.on_lsp_work_start(token.clone(), cx);
|
self.on_lsp_work_start(language_server_id, token.clone(), cx);
|
||||||
self.send_lsp_event(proto::lsp_event::Variant::WorkStart(
|
self.broadcast_lsp_event(
|
||||||
proto::LspWorkStart { token },
|
language_server_id,
|
||||||
));
|
proto::lsp_event::Variant::WorkStart(proto::LspWorkStart { token }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LspEvent::WorkProgress {
|
LspEvent::WorkProgress { token, progress } => {
|
||||||
token,
|
|
||||||
message,
|
|
||||||
percentage,
|
|
||||||
} => {
|
|
||||||
if Some(&token) != disk_diagnostics_token {
|
if Some(&token) != disk_diagnostics_token {
|
||||||
self.on_lsp_work_progress(token.clone(), message.clone(), percentage, cx);
|
self.on_lsp_work_progress(
|
||||||
self.send_lsp_event(proto::lsp_event::Variant::WorkProgress(
|
language_server_id,
|
||||||
proto::LspWorkProgress {
|
token.clone(),
|
||||||
|
progress.clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
self.broadcast_lsp_event(
|
||||||
|
language_server_id,
|
||||||
|
proto::lsp_event::Variant::WorkProgress(proto::LspWorkProgress {
|
||||||
token,
|
token,
|
||||||
message,
|
message: progress.message,
|
||||||
percentage: percentage.map(|p| p as u32),
|
percentage: progress.percentage.map(|p| p as u32),
|
||||||
},
|
}),
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LspEvent::WorkEnd { token } => {
|
LspEvent::WorkEnd { token } => {
|
||||||
if Some(&token) == disk_diagnostics_token {
|
if Some(&token) == disk_diagnostics_token {
|
||||||
self.disk_based_diagnostics_finished(cx);
|
self.disk_based_diagnostics_finished(cx);
|
||||||
self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(
|
self.broadcast_lsp_event(
|
||||||
proto::LspDiskBasedDiagnosticsUpdated {},
|
language_server_id,
|
||||||
));
|
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(
|
||||||
|
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
self.on_lsp_work_end(token.clone(), cx);
|
self.on_lsp_work_end(language_server_id, token.clone(), cx);
|
||||||
self.send_lsp_event(proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd {
|
self.broadcast_lsp_event(
|
||||||
token,
|
language_server_id,
|
||||||
}));
|
proto::lsp_event::Variant::WorkEnd(proto::LspWorkEnd { token }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LspEvent::DiagnosticsUpdate(mut params) => {
|
LspEvent::DiagnosticsUpdate(mut params) => {
|
||||||
|
@ -1362,9 +1389,12 @@ impl Project {
|
||||||
|
|
||||||
if disk_diagnostics_token.is_none() {
|
if disk_diagnostics_token.is_none() {
|
||||||
self.disk_based_diagnostics_started(cx);
|
self.disk_based_diagnostics_started(cx);
|
||||||
self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(
|
self.broadcast_lsp_event(
|
||||||
proto::LspDiskBasedDiagnosticsUpdating {},
|
language_server_id,
|
||||||
));
|
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(
|
||||||
|
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
self.update_diagnostics(
|
self.update_diagnostics(
|
||||||
params,
|
params,
|
||||||
|
@ -1376,38 +1406,74 @@ impl Project {
|
||||||
.log_err();
|
.log_err();
|
||||||
if disk_diagnostics_token.is_none() {
|
if disk_diagnostics_token.is_none() {
|
||||||
self.disk_based_diagnostics_finished(cx);
|
self.disk_based_diagnostics_finished(cx);
|
||||||
self.send_lsp_event(proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(
|
self.broadcast_lsp_event(
|
||||||
proto::LspDiskBasedDiagnosticsUpdated {},
|
language_server_id,
|
||||||
));
|
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdated(
|
||||||
|
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_lsp_work_start(&mut self, token: String, cx: &mut ModelContext<Self>) {}
|
fn on_lsp_work_start(
|
||||||
|
&mut self,
|
||||||
|
language_server_id: usize,
|
||||||
|
token: String,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
self.pending_language_server_work.insert(
|
||||||
|
(language_server_id, token),
|
||||||
|
LspWorkProgress {
|
||||||
|
message: None,
|
||||||
|
percentage: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
fn on_lsp_work_progress(
|
fn on_lsp_work_progress(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
language_server_id: usize,
|
||||||
token: String,
|
token: String,
|
||||||
message: Option<String>,
|
progress: LspWorkProgress,
|
||||||
percentage: Option<usize>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
self.pending_language_server_work
|
||||||
|
.insert((language_server_id, token), progress);
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_lsp_work_end(&mut self, token: String, cx: &mut ModelContext<Self>) {}
|
fn on_lsp_work_end(
|
||||||
|
&mut self,
|
||||||
|
language_server_id: usize,
|
||||||
|
token: String,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
self.pending_language_server_work
|
||||||
|
.remove(&(language_server_id, token));
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
fn send_lsp_event(&self, event: proto::lsp_event::Variant) {
|
fn broadcast_lsp_event(&self, language_server_id: usize, event: proto::lsp_event::Variant) {
|
||||||
if let Some(project_id) = self.remote_id() {
|
if let Some(project_id) = self.remote_id() {
|
||||||
self.client
|
self.client
|
||||||
.send(proto::LspEvent {
|
.send(proto::LspEvent {
|
||||||
project_id,
|
project_id,
|
||||||
|
language_server_id: language_server_id as u64,
|
||||||
variant: Some(event),
|
variant: Some(event),
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pending_language_server_work(&self) -> impl Iterator<Item = (&str, &LspWorkProgress)> {
|
||||||
|
self.pending_language_server_work
|
||||||
|
.iter()
|
||||||
|
.map(|((_, token), progress)| (token.as_str(), progress))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_diagnostics(
|
pub fn update_diagnostics(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: lsp::PublishDiagnosticsParams,
|
params: lsp::PublishDiagnosticsParams,
|
||||||
|
@ -3152,24 +3218,28 @@ impl Project {
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let language_server_id = envelope.payload.language_server_id as usize;
|
||||||
match envelope
|
match envelope
|
||||||
.payload
|
.payload
|
||||||
.variant
|
.variant
|
||||||
.ok_or_else(|| anyhow!("invalid variant"))?
|
.ok_or_else(|| anyhow!("invalid variant"))?
|
||||||
{
|
{
|
||||||
proto::lsp_event::Variant::WorkStart(payload) => this.update(&mut cx, |this, cx| {
|
proto::lsp_event::Variant::WorkStart(payload) => this.update(&mut cx, |this, cx| {
|
||||||
this.on_lsp_work_start(payload.token, cx);
|
this.on_lsp_work_start(language_server_id, payload.token, cx);
|
||||||
}),
|
}),
|
||||||
proto::lsp_event::Variant::WorkProgress(payload) => this.update(&mut cx, |this, cx| {
|
proto::lsp_event::Variant::WorkProgress(payload) => this.update(&mut cx, |this, cx| {
|
||||||
this.on_lsp_work_progress(
|
this.on_lsp_work_progress(
|
||||||
|
language_server_id,
|
||||||
payload.token,
|
payload.token,
|
||||||
payload.message,
|
LspWorkProgress {
|
||||||
payload.percentage.map(|p| p as usize),
|
message: payload.message,
|
||||||
|
percentage: payload.percentage.map(|p| p as usize),
|
||||||
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
proto::lsp_event::Variant::WorkEnd(payload) => this.update(&mut cx, |this, cx| {
|
proto::lsp_event::Variant::WorkEnd(payload) => this.update(&mut cx, |this, cx| {
|
||||||
this.on_lsp_work_end(payload.token, cx);
|
this.on_lsp_work_end(language_server_id, payload.token, cx);
|
||||||
}),
|
}),
|
||||||
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(_) => {
|
proto::lsp_event::Variant::DiskBasedDiagnosticsUpdating(_) => {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
|
|
|
@ -425,12 +425,13 @@ message DiagnosticSummary {
|
||||||
|
|
||||||
message LspEvent {
|
message LspEvent {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
|
uint64 language_server_id = 2;
|
||||||
oneof variant {
|
oneof variant {
|
||||||
LspWorkStart work_start = 2;
|
LspWorkStart work_start = 3;
|
||||||
LspWorkProgress work_progress = 3;
|
LspWorkProgress work_progress = 4;
|
||||||
LspWorkEnd work_end = 4;
|
LspWorkEnd work_end = 5;
|
||||||
LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 5;
|
LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 6;
|
||||||
LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 6;
|
LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use crate::{ItemViewHandle, Settings, StatusItemView};
|
use crate::{ItemViewHandle, Settings, StatusItemView};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action, elements::*, platform::CursorStyle, Entity, MutableAppContext, RenderContext, View,
|
action, elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext,
|
||||||
ViewContext,
|
RenderContext, View, ViewContext,
|
||||||
};
|
};
|
||||||
use language::{LanguageRegistry, LanguageServerBinaryStatus};
|
use language::{LanguageRegistry, LanguageServerBinaryStatus};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
|
use project::Project;
|
||||||
|
use std::fmt::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
action!(DismissErrorMessage);
|
action!(DismissErrorMessage);
|
||||||
|
@ -15,6 +17,7 @@ pub struct LspStatus {
|
||||||
checking_for_update: Vec<String>,
|
checking_for_update: Vec<String>,
|
||||||
downloading: Vec<String>,
|
downloading: Vec<String>,
|
||||||
failed: Vec<String>,
|
failed: Vec<String>,
|
||||||
|
project: ModelHandle<Project>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
@ -23,6 +26,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
|
||||||
impl LspStatus {
|
impl LspStatus {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
project: &ModelHandle<Project>,
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
settings_rx: watch::Receiver<Settings>,
|
settings_rx: watch::Receiver<Settings>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
|
@ -62,11 +66,14 @@ impl LspStatus {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
cx.observe(project, |_, _, cx| cx.notify()).detach();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
settings_rx,
|
settings_rx,
|
||||||
checking_for_update: Default::default(),
|
checking_for_update: Default::default(),
|
||||||
downloading: Default::default(),
|
downloading: Default::default(),
|
||||||
failed: Default::default(),
|
failed: Default::default(),
|
||||||
|
project: project.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +94,24 @@ impl View for LspStatus {
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||||
let theme = &self.settings_rx.borrow().theme;
|
let theme = &self.settings_rx.borrow().theme;
|
||||||
if !self.downloading.is_empty() {
|
|
||||||
|
let mut pending_work = self.project.read(cx).pending_language_server_work();
|
||||||
|
if let Some((progress_token, progress)) = pending_work.next() {
|
||||||
|
let mut message = progress
|
||||||
|
.message
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| progress_token.to_string());
|
||||||
|
if let Some(percentage) = progress.percentage {
|
||||||
|
write!(&mut message, " ({}%)", percentage).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let additional_work_count = pending_work.count();
|
||||||
|
if additional_work_count > 0 {
|
||||||
|
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Label::new(message, theme.workspace.status_bar.lsp_message.clone()).boxed()
|
||||||
|
} else if !self.downloading.is_empty() {
|
||||||
Label::new(
|
Label::new(
|
||||||
format!(
|
format!(
|
||||||
"Downloading {} language server{}...",
|
"Downloading {} language server{}...",
|
||||||
|
@ -112,6 +136,7 @@ impl View for LspStatus {
|
||||||
)
|
)
|
||||||
.boxed()
|
.boxed()
|
||||||
} else if !self.failed.is_empty() {
|
} else if !self.failed.is_empty() {
|
||||||
|
drop(pending_work);
|
||||||
MouseEventHandler::new::<Self, _, _>(0, cx, |_, _| {
|
MouseEventHandler::new::<Self, _, _>(0, cx, |_, _| {
|
||||||
Label::new(
|
Label::new(
|
||||||
format!(
|
format!(
|
||||||
|
|
|
@ -101,6 +101,7 @@ pub fn build_workspace(
|
||||||
});
|
});
|
||||||
let lsp_status = cx.add_view(|cx| {
|
let lsp_status = cx.add_view(|cx| {
|
||||||
workspace::lsp_status::LspStatus::new(
|
workspace::lsp_status::LspStatus::new(
|
||||||
|
workspace.project(),
|
||||||
app_state.languages.clone(),
|
app_state.languages.clone(),
|
||||||
app_state.settings.clone(),
|
app_state.settings.clone(),
|
||||||
cx,
|
cx,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue