Persist LSP logs after log view is closed
This commit is contained in:
parent
6ef6f03322
commit
6ed7f1281f
3 changed files with 293 additions and 140 deletions
|
@ -1,4 +1,4 @@
|
||||||
use collections::HashMap;
|
use collections::{hash_map, HashMap};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use futures::{channel::mpsc, StreamExt};
|
use futures::{channel::mpsc, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -7,15 +7,15 @@ use gpui::{
|
||||||
AnchorCorner, ChildView, Empty, Flex, Label, MouseEventHandler, Overlay, OverlayFitMode,
|
AnchorCorner, ChildView, Empty, Flex, Label, MouseEventHandler, Overlay, OverlayFitMode,
|
||||||
ParentElement, Stack,
|
ParentElement, Stack,
|
||||||
},
|
},
|
||||||
platform::MouseButton,
|
platform::{CursorStyle, MouseButton},
|
||||||
AnyElement, AppContext, Element, Entity, ModelHandle, View, ViewContext, ViewHandle,
|
AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, View, ViewContext,
|
||||||
|
ViewHandle, WeakModelHandle,
|
||||||
};
|
};
|
||||||
use language::{Buffer, LanguageServerId, LanguageServerName};
|
use language::{Buffer, LanguageServerId, LanguageServerName};
|
||||||
use project::{Project, WorktreeId};
|
use project::{Project, WorktreeId};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
use theme::Theme;
|
use theme::{ui, Theme};
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{Item, ItemHandle},
|
item::{Item, ItemHandle},
|
||||||
ToolbarItemLocation, ToolbarItemView, Workspace,
|
ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||||
|
@ -24,11 +24,27 @@ use workspace::{
|
||||||
const SEND_LINE: &str = "// Send:\n";
|
const SEND_LINE: &str = "// Send:\n";
|
||||||
const RECEIVE_LINE: &str = "// Receive:\n";
|
const RECEIVE_LINE: &str = "// Receive:\n";
|
||||||
|
|
||||||
|
struct LogStore {
|
||||||
|
projects: HashMap<WeakModelHandle<Project>, LogStoreProject>,
|
||||||
|
io_tx: mpsc::UnboundedSender<(WeakModelHandle<Project>, LanguageServerId, bool, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LogStoreProject {
|
||||||
|
servers: HashMap<LanguageServerId, LogStoreLanguageServer>,
|
||||||
|
_subscription: gpui::Subscription,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LogStoreLanguageServer {
|
||||||
|
buffer: ModelHandle<Buffer>,
|
||||||
|
last_message_kind: Option<MessageKind>,
|
||||||
|
_subscription: lsp::Subscription,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct LspLogView {
|
pub struct LspLogView {
|
||||||
enabled_logs: HashMap<LanguageServerId, LogState>,
|
log_store: ModelHandle<LogStore>,
|
||||||
current_server_id: Option<LanguageServerId>,
|
current_server_id: Option<LanguageServerId>,
|
||||||
|
editor: Option<ViewHandle<Editor>>,
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
io_tx: mpsc::UnboundedSender<(LanguageServerId, bool, String)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LspLogToolbarItemView {
|
pub struct LspLogToolbarItemView {
|
||||||
|
@ -37,13 +53,6 @@ pub struct LspLogToolbarItemView {
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LogState {
|
|
||||||
buffer: ModelHandle<Buffer>,
|
|
||||||
editor: ViewHandle<Editor>,
|
|
||||||
last_message_kind: Option<MessageKind>,
|
|
||||||
_subscription: lsp::Subscription,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
enum MessageKind {
|
enum MessageKind {
|
||||||
Send,
|
Send,
|
||||||
|
@ -53,26 +62,37 @@ enum MessageKind {
|
||||||
actions!(log, [OpenLanguageServerLogs]);
|
actions!(log, [OpenLanguageServerLogs]);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(LspLogView::open);
|
let log_set = cx.add_model(|cx| LogStore::new(cx));
|
||||||
|
|
||||||
|
cx.add_action(
|
||||||
|
move |workspace: &mut Workspace, _: &OpenLanguageServerLogs, cx: _| {
|
||||||
|
let project = workspace.project().read(cx);
|
||||||
|
if project.is_local() {
|
||||||
|
workspace.add_item(
|
||||||
|
Box::new(cx.add_view(|cx| {
|
||||||
|
LspLogView::new(workspace.project().clone(), log_set.clone(), cx)
|
||||||
|
})),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LspLogView {
|
impl LogStore {
|
||||||
pub fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
|
fn new(cx: &mut ModelContext<Self>) -> Self {
|
||||||
let (io_tx, mut io_rx) = mpsc::unbounded();
|
let (io_tx, mut io_rx) = mpsc::unbounded();
|
||||||
let this = Self {
|
let this = Self {
|
||||||
enabled_logs: HashMap::default(),
|
projects: HashMap::default(),
|
||||||
current_server_id: None,
|
|
||||||
io_tx,
|
io_tx,
|
||||||
project,
|
|
||||||
};
|
};
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
while let Some((language_server_id, is_output, mut message)) = io_rx.next().await {
|
while let Some((project, server_id, is_output, mut message)) = io_rx.next().await {
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
message.push('\n');
|
message.push('\n');
|
||||||
this.on_io(language_server_id, is_output, &message, cx);
|
this.on_io(project, server_id, is_output, &message, cx);
|
||||||
})
|
});
|
||||||
.log_err();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
@ -81,95 +101,155 @@ impl LspLogView {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(
|
pub fn has_enabled_logs_for_language_server(
|
||||||
workspace: &mut Workspace,
|
&self,
|
||||||
_: &OpenLanguageServerLogs,
|
project: &ModelHandle<Project>,
|
||||||
cx: &mut ViewContext<Workspace>,
|
server_id: LanguageServerId,
|
||||||
) {
|
) -> bool {
|
||||||
let project = workspace.project().read(cx);
|
self.projects
|
||||||
if project.is_remote() {
|
.get(&project.downgrade())
|
||||||
return;
|
.map_or(false, |store| store.servers.contains_key(&server_id))
|
||||||
}
|
|
||||||
|
|
||||||
let log_view = cx.add_view(|cx| Self::new(workspace.project().clone(), cx));
|
|
||||||
workspace.add_item(Box::new(log_view), cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn activate_log(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
|
|
||||||
self.enable_logs_for_language_server(server_id, cx);
|
|
||||||
self.current_server_id = Some(server_id);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_io(
|
|
||||||
&mut self,
|
|
||||||
language_server_id: LanguageServerId,
|
|
||||||
is_received: bool,
|
|
||||||
message: &str,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) {
|
|
||||||
if let Some(state) = self.enabled_logs.get_mut(&language_server_id) {
|
|
||||||
state.buffer.update(cx, |buffer, cx| {
|
|
||||||
let kind = if is_received {
|
|
||||||
MessageKind::Receive
|
|
||||||
} else {
|
|
||||||
MessageKind::Send
|
|
||||||
};
|
|
||||||
if state.last_message_kind != Some(kind) {
|
|
||||||
let len = buffer.len();
|
|
||||||
let line = match kind {
|
|
||||||
MessageKind::Send => SEND_LINE,
|
|
||||||
MessageKind::Receive => RECEIVE_LINE,
|
|
||||||
};
|
|
||||||
buffer.edit([(len..len, line)], None, cx);
|
|
||||||
state.last_message_kind = Some(kind);
|
|
||||||
}
|
|
||||||
let len = buffer.len();
|
|
||||||
buffer.edit([(len..len, message)], None, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enable_logs_for_language_server(
|
pub fn enable_logs_for_language_server(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
project: &ModelHandle<Project>,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Option<ModelHandle<Buffer>> {
|
||||||
|
let server = project.read(cx).language_server_for_id(server_id)?;
|
||||||
|
let weak_project = project.downgrade();
|
||||||
|
let project_logs = match self.projects.entry(weak_project) {
|
||||||
|
hash_map::Entry::Occupied(entry) => entry.into_mut(),
|
||||||
|
hash_map::Entry::Vacant(entry) => entry.insert(LogStoreProject {
|
||||||
|
servers: HashMap::default(),
|
||||||
|
_subscription: cx.observe_release(&project, move |this, _, cx| {
|
||||||
|
this.projects.remove(&weak_project);
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let server_log_state = project_logs.servers.entry(server_id).or_insert_with(|| {
|
||||||
|
let io_tx = self.io_tx.clone();
|
||||||
|
let language = project.read(cx).languages().language_for_name("JSON");
|
||||||
|
let buffer = cx.add_model(|cx| Buffer::new(0, "", cx));
|
||||||
|
cx.spawn_weak({
|
||||||
|
let buffer = buffer.clone();
|
||||||
|
|_, mut cx| async move {
|
||||||
|
let language = language.await.ok();
|
||||||
|
buffer.update(&mut cx, |buffer, cx| {
|
||||||
|
buffer.set_language(language, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
let project = project.downgrade();
|
||||||
|
LogStoreLanguageServer {
|
||||||
|
buffer,
|
||||||
|
last_message_kind: None,
|
||||||
|
_subscription: server.on_io(move |is_received, json| {
|
||||||
|
io_tx
|
||||||
|
.unbounded_send((project, server_id, is_received, json.to_string()))
|
||||||
|
.ok();
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Some(server_log_state.buffer.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_logs_for_language_server(
|
||||||
|
&mut self,
|
||||||
|
project: &ModelHandle<Project>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
let project = project.downgrade();
|
||||||
|
if let Some(store) = self.projects.get_mut(&project) {
|
||||||
|
store.servers.remove(&server_id);
|
||||||
|
if store.servers.is_empty() {
|
||||||
|
self.projects.remove(&project);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_io(
|
||||||
|
&mut self,
|
||||||
|
project: WeakModelHandle<Project>,
|
||||||
|
language_server_id: LanguageServerId,
|
||||||
|
is_received: bool,
|
||||||
|
message: &str,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<()> {
|
||||||
|
let state = self
|
||||||
|
.projects
|
||||||
|
.get_mut(&project)?
|
||||||
|
.servers
|
||||||
|
.get_mut(&language_server_id)?;
|
||||||
|
state.buffer.update(cx, |buffer, cx| {
|
||||||
|
let kind = if is_received {
|
||||||
|
MessageKind::Receive
|
||||||
|
} else {
|
||||||
|
MessageKind::Send
|
||||||
|
};
|
||||||
|
if state.last_message_kind != Some(kind) {
|
||||||
|
let len = buffer.len();
|
||||||
|
let line = match kind {
|
||||||
|
MessageKind::Send => SEND_LINE,
|
||||||
|
MessageKind::Receive => RECEIVE_LINE,
|
||||||
|
};
|
||||||
|
buffer.edit([(len..len, line)], None, cx);
|
||||||
|
state.last_message_kind = Some(kind);
|
||||||
|
}
|
||||||
|
let len = buffer.len();
|
||||||
|
buffer.edit([(len..len, message)], None, cx);
|
||||||
|
});
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LspLogView {
|
||||||
|
fn new(
|
||||||
|
project: ModelHandle<Project>,
|
||||||
|
log_set: ModelHandle<LogStore>,
|
||||||
|
_: &mut ViewContext<Self>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
project,
|
||||||
|
log_store: log_set,
|
||||||
|
editor: None,
|
||||||
|
current_server_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
|
||||||
|
let buffer = self.log_store.update(cx, |log_set, cx| {
|
||||||
|
log_set.enable_logs_for_language_server(&self.project, server_id, cx)
|
||||||
|
});
|
||||||
|
if let Some(buffer) = buffer {
|
||||||
|
self.current_server_id = Some(server_id);
|
||||||
|
self.editor = Some(cx.add_view(|cx| {
|
||||||
|
let mut editor = Editor::for_buffer(buffer, Some(self.project.clone()), cx);
|
||||||
|
editor.set_read_only(true);
|
||||||
|
editor.move_to_end(&Default::default(), cx);
|
||||||
|
editor
|
||||||
|
}));
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_logging_for_server(
|
||||||
|
&mut self,
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
enabled: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if let Some(server) = self.project.read(cx).language_server_for_id(server_id) {
|
self.log_store.update(cx, |log_store, cx| {
|
||||||
self.enabled_logs.entry(server_id).or_insert_with(|| {
|
if enabled {
|
||||||
let project = self.project.read(cx);
|
log_store.enable_logs_for_language_server(&self.project, server_id, cx);
|
||||||
let io_tx = self.io_tx.clone();
|
} else {
|
||||||
let language = project.languages().language_for_name("JSON");
|
log_store.disable_logs_for_language_server(&self.project, server_id, cx);
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, "", cx));
|
}
|
||||||
cx.spawn({
|
});
|
||||||
let buffer = buffer.clone();
|
|
||||||
|_, mut cx| async move {
|
|
||||||
let language = language.await.ok();
|
|
||||||
buffer.update(&mut cx, |buffer, cx| {
|
|
||||||
buffer.set_language(language, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
let editor = cx.add_view(|cx| {
|
|
||||||
let mut editor =
|
|
||||||
Editor::for_buffer(buffer.clone(), Some(self.project.clone()), cx);
|
|
||||||
editor.set_read_only(true);
|
|
||||||
editor
|
|
||||||
});
|
|
||||||
|
|
||||||
LogState {
|
|
||||||
buffer,
|
|
||||||
editor,
|
|
||||||
last_message_kind: None,
|
|
||||||
_subscription: server.on_io(move |is_received, json| {
|
|
||||||
io_tx
|
|
||||||
.unbounded_send((server_id, is_received, json.to_string()))
|
|
||||||
.ok();
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,12 +259,11 @@ impl View for LspLogView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
if let Some(id) = self.current_server_id {
|
if let Some(editor) = &self.editor {
|
||||||
if let Some(log) = self.enabled_logs.get_mut(&id) {
|
ChildView::new(&editor, cx).into_any()
|
||||||
return ChildView::new(&log.editor, cx).into_any();
|
} else {
|
||||||
}
|
Empty::new().into_any()
|
||||||
}
|
}
|
||||||
Empty::new().into_any()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +274,7 @@ impl Item for LspLogView {
|
||||||
style: &theme::Tab,
|
style: &theme::Tab,
|
||||||
_: &AppContext,
|
_: &AppContext,
|
||||||
) -> AnyElement<V> {
|
) -> AnyElement<V> {
|
||||||
Label::new("Logs", style.label.clone()).into_any()
|
Label::new("LSP Logs", style.label.clone()).into_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,10 +307,24 @@ impl View for LspLogToolbarItemView {
|
||||||
let theme = cx.global::<Settings>().theme.clone();
|
let theme = cx.global::<Settings>().theme.clone();
|
||||||
let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() };
|
let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() };
|
||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
let mut language_servers = project.language_servers().collect::<Vec<_>>();
|
let log_view = log_view.read(cx);
|
||||||
language_servers.sort_by_key(|a| a.0);
|
let log_store = log_view.log_store.read(cx);
|
||||||
|
|
||||||
let current_server_id = log_view.read(cx).current_server_id;
|
let mut language_servers = project
|
||||||
|
.language_servers()
|
||||||
|
.map(|(id, name, worktree)| {
|
||||||
|
(
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
worktree,
|
||||||
|
log_store.has_enabled_logs_for_language_server(&self.project, id),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
language_servers.sort_by_key(|a| (a.0, a.2));
|
||||||
|
language_servers.dedup_by_key(|a| a.0);
|
||||||
|
|
||||||
|
let current_server_id = log_view.current_server_id;
|
||||||
let current_server = current_server_id.and_then(|current_server_id| {
|
let current_server = current_server_id.and_then(|current_server_id| {
|
||||||
if let Ok(ix) = language_servers.binary_search_by_key(¤t_server_id, |e| e.0) {
|
if let Ok(ix) = language_servers.binary_search_by_key(¤t_server_id, |e| e.0) {
|
||||||
Some(language_servers[ix].clone())
|
Some(language_servers[ix].clone())
|
||||||
|
@ -252,11 +345,13 @@ impl View for LspLogToolbarItemView {
|
||||||
Overlay::new(
|
Overlay::new(
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_children(language_servers.into_iter().filter_map(
|
.with_children(language_servers.into_iter().filter_map(
|
||||||
|(id, name, worktree_id)| {
|
|(id, name, worktree_id, logging_enabled)| {
|
||||||
Self::render_language_server_menu_item(
|
Self::render_language_server_menu_item(
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
worktree_id,
|
worktree_id,
|
||||||
|
logging_enabled,
|
||||||
|
Some(id) == current_server_id,
|
||||||
&self.project,
|
&self.project,
|
||||||
&theme,
|
&theme,
|
||||||
cx,
|
cx,
|
||||||
|
@ -266,19 +361,22 @@ impl View for LspLogToolbarItemView {
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(theme.contacts_popover.container)
|
.with_style(theme.contacts_popover.container)
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_width(200.)
|
.with_width(400.)
|
||||||
.with_height(400.),
|
.with_height(400.),
|
||||||
)
|
)
|
||||||
.with_fit_mode(OverlayFitMode::SwitchAnchor)
|
.with_fit_mode(OverlayFitMode::SwitchAnchor)
|
||||||
.with_anchor_corner(AnchorCorner::TopRight)
|
.with_anchor_corner(AnchorCorner::TopLeft)
|
||||||
.with_z_index(999)
|
.with_z_index(999)
|
||||||
.aligned()
|
.aligned()
|
||||||
.bottom()
|
.bottom()
|
||||||
.right(),
|
.left(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
|
.aligned()
|
||||||
|
.left()
|
||||||
|
.clipped()
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,10 +395,29 @@ impl LspLogToolbarItemView {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate_log_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
|
fn toggle_logging_for_server(
|
||||||
|
&mut self,
|
||||||
|
id: LanguageServerId,
|
||||||
|
enabled: bool,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
if let Some(log_view) = &self.log_view {
|
if let Some(log_view) = &self.log_view {
|
||||||
log_view.update(cx, |log_view, cx| {
|
log_view.update(cx, |log_view, cx| {
|
||||||
log_view.activate_log(id, cx);
|
log_view.toggle_logging_for_server(id, enabled, cx);
|
||||||
|
if !enabled && Some(id) == log_view.current_server_id {
|
||||||
|
log_view.current_server_id = None;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.menu_open = false;
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_logs_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(log_view) = &self.log_view {
|
||||||
|
log_view.update(cx, |log_view, cx| {
|
||||||
|
log_view.show_logs_for_server(id, cx);
|
||||||
});
|
});
|
||||||
self.menu_open = false;
|
self.menu_open = false;
|
||||||
}
|
}
|
||||||
|
@ -308,7 +425,7 @@ impl LspLogToolbarItemView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_language_server_menu_header(
|
fn render_language_server_menu_header(
|
||||||
current_server: Option<(LanguageServerId, LanguageServerName, WorktreeId)>,
|
current_server: Option<(LanguageServerId, LanguageServerName, WorktreeId, bool)>,
|
||||||
project: &ModelHandle<Project>,
|
project: &ModelHandle<Project>,
|
||||||
theme: &Arc<Theme>,
|
theme: &Arc<Theme>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
|
@ -317,7 +434,7 @@ impl LspLogToolbarItemView {
|
||||||
MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, cx| {
|
MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, cx| {
|
||||||
let project = project.read(cx);
|
let project = project.read(cx);
|
||||||
let label: Cow<str> = current_server
|
let label: Cow<str> = current_server
|
||||||
.and_then(|(_, server_name, worktree_id)| {
|
.and_then(|(_, server_name, worktree_id, _)| {
|
||||||
let worktree = project.worktree_for_id(worktree_id, cx)?;
|
let worktree = project.worktree_for_id(worktree_id, cx)?;
|
||||||
let worktree = &worktree.read(cx);
|
let worktree = &worktree.read(cx);
|
||||||
Some(format!("{} - ({})", server_name.0, worktree.root_name()).into())
|
Some(format!("{} - ({})", server_name.0, worktree.root_name()).into())
|
||||||
|
@ -325,6 +442,7 @@ impl LspLogToolbarItemView {
|
||||||
.unwrap_or_else(|| "No server selected".into());
|
.unwrap_or_else(|| "No server selected".into());
|
||||||
Label::new(label, theme.context_menu.item.default.label.clone())
|
Label::new(label, theme.context_menu.item.default.label.clone())
|
||||||
})
|
})
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.on_click(MouseButton::Left, move |_, view, cx| {
|
.on_click(MouseButton::Left, move |_, view, cx| {
|
||||||
view.toggle_menu(cx);
|
view.toggle_menu(cx);
|
||||||
})
|
})
|
||||||
|
@ -334,6 +452,8 @@ impl LspLogToolbarItemView {
|
||||||
id: LanguageServerId,
|
id: LanguageServerId,
|
||||||
name: LanguageServerName,
|
name: LanguageServerName,
|
||||||
worktree_id: WorktreeId,
|
worktree_id: WorktreeId,
|
||||||
|
logging_enabled: bool,
|
||||||
|
is_selected: bool,
|
||||||
project: &ModelHandle<Project>,
|
project: &ModelHandle<Project>,
|
||||||
theme: &Arc<Theme>,
|
theme: &Arc<Theme>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
|
@ -349,15 +469,34 @@ impl LspLogToolbarItemView {
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
MouseEventHandler::<ActivateLog, _>::new(id.0, cx, move |state, cx| {
|
MouseEventHandler::<ActivateLog, _>::new(id.0, cx, move |state, cx| {
|
||||||
Label::new(label, theme.context_menu.item.default.label.clone())
|
let item_style = theme.context_menu.item.style_for(state, is_selected);
|
||||||
|
Flex::row()
|
||||||
|
.with_child(ui::checkbox_with_label::<Self, _, Self, _>(
|
||||||
|
Empty::new(),
|
||||||
|
&theme.welcome.checkbox,
|
||||||
|
logging_enabled,
|
||||||
|
id.0,
|
||||||
|
cx,
|
||||||
|
move |this, enabled, cx| {
|
||||||
|
this.toggle_logging_for_server(id, enabled, cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_child(Label::new(label, item_style.label.clone()))
|
||||||
|
.contained()
|
||||||
|
.with_style(item_style.container)
|
||||||
})
|
})
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.on_click(MouseButton::Left, move |_, view, cx| {
|
.on_click(MouseButton::Left, move |_, view, cx| {
|
||||||
view.activate_log_for_server(id, cx);
|
view.show_logs_for_server(id, cx);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Entity for LogStore {
|
||||||
|
type Event = ();
|
||||||
|
}
|
||||||
|
|
||||||
impl Entity for LspLogView {
|
impl Entity for LspLogView {
|
||||||
type Event = ();
|
type Event = ();
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,28 +27,40 @@ pub struct CheckboxStyle {
|
||||||
pub hovered_and_checked: ContainerStyle,
|
pub hovered_and_checked: ContainerStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn checkbox<Tag: 'static, V: View>(
|
pub fn checkbox<Tag, V, F>(
|
||||||
label: &'static str,
|
label: &'static str,
|
||||||
style: &CheckboxStyle,
|
style: &CheckboxStyle,
|
||||||
checked: bool,
|
checked: bool,
|
||||||
|
id: usize,
|
||||||
cx: &mut ViewContext<V>,
|
cx: &mut ViewContext<V>,
|
||||||
change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
|
change: F,
|
||||||
) -> MouseEventHandler<Tag, V> {
|
) -> MouseEventHandler<Tag, V>
|
||||||
|
where
|
||||||
|
Tag: 'static,
|
||||||
|
V: View,
|
||||||
|
F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
|
||||||
|
{
|
||||||
let label = Label::new(label, style.label.text.clone())
|
let label = Label::new(label, style.label.text.clone())
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(style.label.container);
|
.with_style(style.label.container);
|
||||||
|
checkbox_with_label(label, style, checked, id, cx, change)
|
||||||
checkbox_with_label(label, style, checked, cx, change)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn checkbox_with_label<Tag: 'static, D: Element<V>, V: View>(
|
pub fn checkbox_with_label<Tag, D, V, F>(
|
||||||
label: D,
|
label: D,
|
||||||
style: &CheckboxStyle,
|
style: &CheckboxStyle,
|
||||||
checked: bool,
|
checked: bool,
|
||||||
|
id: usize,
|
||||||
cx: &mut ViewContext<V>,
|
cx: &mut ViewContext<V>,
|
||||||
change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
|
change: F,
|
||||||
) -> MouseEventHandler<Tag, V> {
|
) -> MouseEventHandler<Tag, V>
|
||||||
MouseEventHandler::new(0, cx, |state, _| {
|
where
|
||||||
|
Tag: 'static,
|
||||||
|
D: Element<V>,
|
||||||
|
V: View,
|
||||||
|
F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
|
||||||
|
{
|
||||||
|
MouseEventHandler::new(id, cx, |state, _| {
|
||||||
let indicator = if checked {
|
let indicator = if checked {
|
||||||
svg(&style.icon)
|
svg(&style.icon)
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,8 +87,8 @@ pub fn checkbox_with_label<Tag: 'static, D: Element<V>, V: View>(
|
||||||
.with_child(label)
|
.with_child(label)
|
||||||
.align_children_center()
|
.align_children_center()
|
||||||
})
|
})
|
||||||
.on_click(platform::MouseButton::Left, move |_, _, cx| {
|
.on_click(platform::MouseButton::Left, move |_, view, cx| {
|
||||||
change(!checked, cx)
|
change(view, !checked, cx)
|
||||||
})
|
})
|
||||||
.with_cursor_style(platform::CursorStyle::PointingHand)
|
.with_cursor_style(platform::CursorStyle::PointingHand)
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ impl View for WelcomePage {
|
||||||
.with_child(
|
.with_child(
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_child(
|
.with_child(
|
||||||
theme::ui::checkbox_with_label::<Metrics, _, Self>(
|
theme::ui::checkbox_with_label::<Metrics, _, Self, _>(
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_child(
|
.with_child(
|
||||||
Label::new(
|
Label::new(
|
||||||
|
@ -146,8 +146,9 @@ impl View for WelcomePage {
|
||||||
),
|
),
|
||||||
&theme.welcome.checkbox,
|
&theme.welcome.checkbox,
|
||||||
metrics,
|
metrics,
|
||||||
|
0,
|
||||||
cx,
|
cx,
|
||||||
|checked, cx| {
|
|_, checked, cx| {
|
||||||
SettingsFile::update(cx, move |file| {
|
SettingsFile::update(cx, move |file| {
|
||||||
file.telemetry.set_metrics(checked)
|
file.telemetry.set_metrics(checked)
|
||||||
})
|
})
|
||||||
|
@ -157,12 +158,13 @@ impl View for WelcomePage {
|
||||||
.with_style(theme.welcome.checkbox_container),
|
.with_style(theme.welcome.checkbox_container),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
theme::ui::checkbox::<Diagnostics, Self>(
|
theme::ui::checkbox::<Diagnostics, Self, _>(
|
||||||
"Send crash reports",
|
"Send crash reports",
|
||||||
&theme.welcome.checkbox,
|
&theme.welcome.checkbox,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
|
0,
|
||||||
cx,
|
cx,
|
||||||
|checked, cx| {
|
|_, checked, cx| {
|
||||||
SettingsFile::update(cx, move |file| {
|
SettingsFile::update(cx, move |file| {
|
||||||
file.telemetry.set_diagnostics(checked)
|
file.telemetry.set_diagnostics(checked)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue