Detailed changes
@@ -3612,6 +3612,26 @@ dependencies = [
"url",
]
+[[package]]
+name = "lsp_log"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "collections",
+ "editor",
+ "futures 0.3.25",
+ "gpui",
+ "language",
+ "lsp",
+ "project",
+ "serde",
+ "settings",
+ "theme",
+ "unindent",
+ "util",
+ "workspace",
+]
+
[[package]]
name = "mach"
version = "0.3.2"
@@ -7239,7 +7259,7 @@ dependencies = [
[[package]]
name = "tree-sitter-json"
version = "0.20.0"
-source = "git+https://github.com/tree-sitter/tree-sitter-json?rev=137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8#137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8"
+source = "git+https://github.com/tree-sitter/tree-sitter-json?rev=40a81c01a40ac48744e0c8ccabbaba1920441199#40a81c01a40ac48744e0c8ccabbaba1920441199"
dependencies = [
"cc",
"tree-sitter",
@@ -8571,6 +8591,7 @@ dependencies = [
"libc",
"log",
"lsp",
+ "lsp_log",
"node_runtime",
"num_cpus",
"outline",
@@ -35,6 +35,7 @@ members = [
"crates/live_kit_client",
"crates/live_kit_server",
"crates/lsp",
+ "crates/lsp_log",
"crates/media",
"crates/menu",
"crates/node_runtime",
@@ -77,6 +78,7 @@ serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
rand = { version = "0.8" }
postage = { version = "0.5", features = ["futures-traits"] }
smallvec = { version = "1.6", features = ["union"] }
+futures = { version = "0.3" }
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" }
@@ -17,5 +17,5 @@ project = { path = "../project" }
settings = { path = "../settings" }
util = { path = "../util" }
workspace = { path = "../workspace" }
-futures = "0.3"
+futures = { workspace = true }
smallvec = { workspace = true }
@@ -33,7 +33,7 @@ util = { path = "../util" }
anyhow = "1.0.38"
async-broadcast = "0.4"
-futures = "0.3"
+futures = { workspace = true }
postage = { workspace = true }
[dev-dependencies]
@@ -22,7 +22,7 @@ sum_tree = { path = "../sum_tree" }
anyhow = "1.0.38"
async-recursion = "0.3"
async-tungstenite = { version = "0.16", features = ["async-tls"] }
-futures = "0.3"
+futures = { workspace = true }
image = "0.23"
lazy_static = "1.4.0"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
@@ -27,7 +27,7 @@ base64 = "0.13"
clap = { version = "3.1", features = ["derive"], optional = true }
dashmap = "5.4"
envy = "0.4.2"
-futures = "0.3"
+futures = { workspace = true }
hyper = "0.14"
lazy_static = "1.4"
lipsum = { version = "0.8", optional = true }
@@ -40,7 +40,7 @@ theme = { path = "../theme" }
util = { path = "../util" }
workspace = { path = "../workspace" }
anyhow = "1.0"
-futures = "0.3"
+futures = { workspace = true }
log = "0.4"
postage = { workspace = true }
serde = { workspace = true }
@@ -35,7 +35,7 @@ log = "0.4"
serde = { workspace = true }
serde_derive = { workspace = true }
smol = "1.2.5"
-futures = "0.3"
+futures = { workspace = true }
[dev-dependencies]
clock = { path = "../clock" }
@@ -19,4 +19,4 @@ util = { path = "../util" }
workspace = { path = "../workspace" }
anyhow = "1.0"
smol = "1.2.5"
-futures = "0.3"
+futures = { workspace = true }
@@ -47,7 +47,7 @@ workspace = { path = "../workspace" }
aho-corasick = "0.7"
anyhow = "1.0"
-futures = "0.3"
+futures = { workspace = true }
indoc = "1.0.4"
itertools = "0.10"
lazy_static = "1.4"
@@ -511,6 +511,7 @@ pub struct Editor {
workspace_id: Option<WorkspaceId>,
keymap_context_layers: BTreeMap<TypeId, KeymapContext>,
input_enabled: bool,
+ read_only: bool,
leader_replica_id: Option<u16>,
remote_id: Option<ViewId>,
hover_state: HoverState,
@@ -1283,6 +1284,7 @@ impl Editor {
workspace_id: None,
keymap_context_layers: Default::default(),
input_enabled: true,
+ read_only: false,
leader_replica_id: None,
remote_id: None,
hover_state: Default::default(),
@@ -1425,6 +1427,10 @@ impl Editor {
self.input_enabled = input_enabled;
}
+ pub fn set_read_only(&mut self, read_only: bool) {
+ self.read_only = read_only;
+ }
+
fn selections_did_change(
&mut self,
local: bool,
@@ -1533,6 +1539,10 @@ impl Editor {
S: ToOffset,
T: Into<Arc<str>>,
{
+ if self.read_only {
+ return;
+ }
+
self.buffer
.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
}
@@ -1543,6 +1553,10 @@ impl Editor {
S: ToOffset,
T: Into<Arc<str>>,
{
+ if self.read_only {
+ return;
+ }
+
self.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, Some(AutoindentMode::EachLine), cx)
});
@@ -1897,6 +1911,9 @@ impl Editor {
pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
let text: Arc<str> = text.into();
+ if self.read_only {
+ return;
+ }
if !self.input_enabled {
cx.emit(Event::InputIgnored { text });
return;
@@ -2282,6 +2299,10 @@ impl Editor {
autoindent_mode: Option<AutoindentMode>,
cx: &mut ViewContext<Self>,
) {
+ if self.read_only {
+ return;
+ }
+
let text: Arc<str> = text.into();
self.transact(cx, |this, cx| {
let old_selections = this.selections.all_adjusted(cx);
@@ -16,7 +16,7 @@ client = { path = "../client" }
editor = { path = "../editor" }
language = { path = "../language" }
log = "0.4"
-futures = "0.3"
+futures = { workspace = true }
gpui = { path = "../gpui" }
human_bytes = "0.4.1"
isahc = "1.7"
@@ -15,7 +15,7 @@ rope = { path = "../rope" }
util = { path = "../util" }
anyhow = "1.0.57"
async-trait = "0.1"
-futures = "0.3"
+futures = { workspace = true }
tempfile = "3"
fsevent = { path = "../fsevent" }
lazy_static = "1.4.0"
@@ -19,7 +19,7 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
smol = "1.2"
parking_lot = "0.11.1"
async-trait = "0.1"
-futures = "0.3"
+futures = { workspace = true }
git2 = { version = "0.15", default-features = false }
[dev-dependencies]
@@ -25,7 +25,7 @@ ctor = "0.1"
dhat = { version = "0.3", optional = true }
env_logger = { version = "0.9", optional = true }
etagere = "0.2"
-futures = "0.3"
+futures = { workspace = true }
image = "0.23"
itertools = "0.10"
lazy_static = "1.4.0"
@@ -39,7 +39,7 @@ util = { path = "../util" }
anyhow = "1.0.38"
async-broadcast = "0.4"
async-trait = "0.1"
-futures = "0.3"
+futures = { workspace = true }
lazy_static = "1.4"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
parking_lot = "0.11.1"
@@ -32,7 +32,7 @@ anyhow = "1.0.38"
async-broadcast = "0.4"
core-foundation = "0.9.3"
core-graphics = "0.22.3"
-futures = "0.3"
+futures = { workspace = true }
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
parking_lot = "0.11.1"
postage = { workspace = true }
@@ -56,7 +56,7 @@ cocoa = "0.24"
core-foundation = "0.9.3"
core-graphics = "0.22.3"
foreign-types = "0.3"
-futures = "0.3"
+futures = { workspace = true }
hmac = "0.12"
jwt = "0.16"
lazy_static = "1.4"
@@ -12,7 +12,7 @@ doctest = false
[dependencies]
anyhow = "1.0.38"
async-trait = "0.1"
-futures = "0.3"
+futures = { workspace = true }
hmac = "0.12"
log = "0.4"
jwt = "0.16"
@@ -17,7 +17,7 @@ gpui = { path = "../gpui" }
util = { path = "../util" }
anyhow = "1.0"
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true }
-futures = "0.3"
+futures = { workspace = true }
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
lsp-types = "0.91"
parking_lot = "0.11"
@@ -20,10 +20,10 @@ use std::{
future::Future,
io::Write,
path::PathBuf,
- str::FromStr,
+ str::{self, FromStr as _},
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
- Arc,
+ Arc, Weak,
},
};
use std::{path::Path, process::Stdio};
@@ -34,16 +34,18 @@ const CONTENT_LEN_HEADER: &str = "Content-Length: ";
type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppContext)>;
type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
+type IoHandler = Box<dyn Send + FnMut(bool, &str)>;
pub struct LanguageServer {
server_id: LanguageServerId,
next_id: AtomicUsize,
- outbound_tx: channel::Sender<Vec<u8>>,
+ outbound_tx: channel::Sender<String>,
name: String,
capabilities: ServerCapabilities,
code_action_kinds: Option<Vec<CodeActionKind>>,
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
+ io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
executor: Arc<executor::Background>,
#[allow(clippy::type_complexity)]
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
@@ -56,9 +58,15 @@ pub struct LanguageServer {
#[repr(transparent)]
pub struct LanguageServerId(pub usize);
-pub struct Subscription {
- method: &'static str,
- notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
+pub enum Subscription {
+ Notification {
+ method: &'static str,
+ notification_handlers: Option<Arc<Mutex<HashMap<&'static str, NotificationHandler>>>>,
+ },
+ Io {
+ id: usize,
+ io_handlers: Option<Weak<Mutex<HashMap<usize, IoHandler>>>>,
+ },
}
#[derive(Serialize, Deserialize)]
@@ -177,33 +185,40 @@ impl LanguageServer {
Stdout: AsyncRead + Unpin + Send + 'static,
F: FnMut(AnyNotification) + 'static + Send,
{
- let (outbound_tx, outbound_rx) = channel::unbounded::<Vec<u8>>();
+ let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
+ let (output_done_tx, output_done_rx) = barrier::channel();
let notification_handlers =
Arc::new(Mutex::new(HashMap::<_, NotificationHandler>::default()));
let response_handlers =
Arc::new(Mutex::new(Some(HashMap::<_, ResponseHandler>::default())));
+ let io_handlers = Arc::new(Mutex::new(HashMap::default()));
let input_task = cx.spawn(|cx| {
- let notification_handlers = notification_handlers.clone();
- let response_handlers = response_handlers.clone();
Self::handle_input(
stdout,
on_unhandled_notification,
- notification_handlers,
- response_handlers,
+ notification_handlers.clone(),
+ response_handlers.clone(),
+ io_handlers.clone(),
cx,
)
.log_err()
});
- let (output_done_tx, output_done_rx) = barrier::channel();
let output_task = cx.background().spawn({
- let response_handlers = response_handlers.clone();
- Self::handle_output(stdin, outbound_rx, output_done_tx, response_handlers).log_err()
+ Self::handle_output(
+ stdin,
+ outbound_rx,
+ output_done_tx,
+ response_handlers.clone(),
+ io_handlers.clone(),
+ )
+ .log_err()
});
Self {
server_id,
notification_handlers,
response_handlers,
+ io_handlers,
name: Default::default(),
capabilities: Default::default(),
code_action_kinds,
@@ -226,6 +241,7 @@ impl LanguageServer {
mut on_unhandled_notification: F,
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
+ io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
cx: AsyncAppContext,
) -> anyhow::Result<()>
where
@@ -252,7 +268,13 @@ impl LanguageServer {
buffer.resize(message_len, 0);
stdout.read_exact(&mut buffer).await?;
- log::trace!("incoming message:{}", String::from_utf8_lossy(&buffer));
+
+ if let Ok(message) = str::from_utf8(&buffer) {
+ log::trace!("incoming message:{}", message);
+ for handler in io_handlers.lock().values_mut() {
+ handler(true, message);
+ }
+ }
if let Ok(msg) = serde_json::from_slice::<AnyNotification>(&buffer) {
if let Some(handler) = notification_handlers.lock().get_mut(msg.method) {
@@ -291,9 +313,10 @@ impl LanguageServer {
async fn handle_output<Stdin>(
stdin: Stdin,
- outbound_rx: channel::Receiver<Vec<u8>>,
+ outbound_rx: channel::Receiver<String>,
output_done_tx: barrier::Sender,
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
+ io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
) -> anyhow::Result<()>
where
Stdin: AsyncWrite + Unpin + Send + 'static,
@@ -307,13 +330,17 @@ impl LanguageServer {
});
let mut content_len_buffer = Vec::new();
while let Ok(message) = outbound_rx.recv().await {
- log::trace!("outgoing message:{}", String::from_utf8_lossy(&message));
+ log::trace!("outgoing message:{}", message);
+ for handler in io_handlers.lock().values_mut() {
+ handler(false, &message);
+ }
+
content_len_buffer.clear();
write!(content_len_buffer, "{}", message.len()).unwrap();
stdin.write_all(CONTENT_LEN_HEADER.as_bytes()).await?;
stdin.write_all(&content_len_buffer).await?;
stdin.write_all("\r\n\r\n".as_bytes()).await?;
- stdin.write_all(&message).await?;
+ stdin.write_all(message.as_bytes()).await?;
stdin.flush().await?;
}
drop(output_done_tx);
@@ -464,6 +491,19 @@ impl LanguageServer {
self.on_custom_request(T::METHOD, f)
}
+ #[must_use]
+ pub fn on_io<F>(&self, f: F) -> Subscription
+ where
+ F: 'static + Send + FnMut(bool, &str),
+ {
+ let id = self.next_id.fetch_add(1, SeqCst);
+ self.io_handlers.lock().insert(id, Box::new(f));
+ Subscription::Io {
+ id,
+ io_handlers: Some(Arc::downgrade(&self.io_handlers)),
+ }
+ }
+
pub fn remove_request_handler<T: request::Request>(&self) {
self.notification_handlers.lock().remove(T::METHOD);
}
@@ -490,9 +530,9 @@ impl LanguageServer {
prev_handler.is_none(),
"registered multiple handlers for the same LSP method"
);
- Subscription {
+ Subscription::Notification {
method,
- notification_handlers: self.notification_handlers.clone(),
+ notification_handlers: Some(self.notification_handlers.clone()),
}
}
@@ -537,7 +577,7 @@ impl LanguageServer {
},
};
if let Some(response) =
- serde_json::to_vec(&response).log_err()
+ serde_json::to_string(&response).log_err()
{
outbound_tx.try_send(response).ok();
}
@@ -560,7 +600,7 @@ impl LanguageServer {
message: error.to_string(),
}),
};
- if let Some(response) = serde_json::to_vec(&response).log_err() {
+ if let Some(response) = serde_json::to_string(&response).log_err() {
outbound_tx.try_send(response).ok();
}
}
@@ -572,9 +612,9 @@ impl LanguageServer {
prev_handler.is_none(),
"registered multiple handlers for the same LSP method"
);
- Subscription {
+ Subscription::Notification {
method,
- notification_handlers: self.notification_handlers.clone(),
+ notification_handlers: Some(self.notification_handlers.clone()),
}
}
@@ -612,14 +652,14 @@ impl LanguageServer {
fn request_internal<T: request::Request>(
next_id: &AtomicUsize,
response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
- outbound_tx: &channel::Sender<Vec<u8>>,
+ outbound_tx: &channel::Sender<String>,
params: T::Params,
) -> impl 'static + Future<Output = Result<T::Result>>
where
T::Result: 'static + Send,
{
let id = next_id.fetch_add(1, SeqCst);
- let message = serde_json::to_vec(&Request {
+ let message = serde_json::to_string(&Request {
jsonrpc: JSON_RPC_VERSION,
id,
method: T::METHOD,
@@ -662,10 +702,10 @@ impl LanguageServer {
}
fn notify_internal<T: notification::Notification>(
- outbound_tx: &channel::Sender<Vec<u8>>,
+ outbound_tx: &channel::Sender<String>,
params: T::Params,
) -> Result<()> {
- let message = serde_json::to_vec(&Notification {
+ let message = serde_json::to_string(&Notification {
jsonrpc: JSON_RPC_VERSION,
method: T::METHOD,
params,
@@ -685,8 +725,14 @@ impl Drop for LanguageServer {
}
impl Subscription {
- pub fn detach(mut self) {
- self.method = "";
+ pub fn detach(&mut self) {
+ match self {
+ Subscription::Notification {
+ notification_handlers,
+ ..
+ } => *notification_handlers = None,
+ Subscription::Io { io_handlers, .. } => *io_handlers = None,
+ }
}
}
@@ -698,7 +744,21 @@ impl fmt::Display for LanguageServerId {
impl Drop for Subscription {
fn drop(&mut self) {
- self.notification_handlers.lock().remove(self.method);
+ match self {
+ Subscription::Notification {
+ method,
+ notification_handlers,
+ } => {
+ if let Some(handlers) = notification_handlers {
+ handlers.lock().remove(method);
+ }
+ }
+ Subscription::Io { id, io_handlers } => {
+ if let Some(io_handlers) = io_handlers.as_ref().and_then(|h| h.upgrade()) {
+ io_handlers.lock().remove(id);
+ }
+ }
+ }
}
}
@@ -0,0 +1,29 @@
+[package]
+name = "lsp_log"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/lsp_log.rs"
+doctest = false
+
+[dependencies]
+collections = { path = "../collections" }
+editor = { path = "../editor" }
+settings = { path = "../settings" }
+theme = { path = "../theme" }
+language = { path = "../language" }
+project = { path = "../project" }
+workspace = { path = "../workspace" }
+gpui = { path = "../gpui" }
+util = { path = "../util" }
+lsp = { path = "../lsp" }
+futures = { workspace = true }
+serde = { workspace = true }
+anyhow = "1.0"
+
+[dev-dependencies]
+gpui = { path = "../gpui", features = ["test-support"] }
+util = { path = "../util", features = ["test-support"] }
+unindent = "0.1.7"
@@ -0,0 +1,523 @@
+use collections::{hash_map, HashMap};
+use editor::Editor;
+use futures::{channel::mpsc, StreamExt};
+use gpui::{
+ actions,
+ elements::{
+ AnchorCorner, ChildView, Empty, Flex, Label, MouseEventHandler, Overlay, OverlayFitMode,
+ ParentElement, Stack,
+ },
+ platform::{CursorStyle, MouseButton},
+ AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, View, ViewContext,
+ ViewHandle, WeakModelHandle,
+};
+use language::{Buffer, LanguageServerId, LanguageServerName};
+use project::{Project, WorktreeId};
+use settings::Settings;
+use std::{borrow::Cow, sync::Arc};
+use theme::{ui, Theme};
+use workspace::{
+ item::{Item, ItemHandle},
+ ToolbarItemLocation, ToolbarItemView, Workspace,
+};
+
+const SEND_LINE: &str = "// Send:\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 {
+ log_store: ModelHandle<LogStore>,
+ current_server_id: Option<LanguageServerId>,
+ editor: Option<ViewHandle<Editor>>,
+ project: ModelHandle<Project>,
+}
+
+pub struct LspLogToolbarItemView {
+ log_view: Option<ViewHandle<LspLogView>>,
+ menu_open: bool,
+ project: ModelHandle<Project>,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+enum MessageKind {
+ Send,
+ Receive,
+}
+
+actions!(log, [OpenLanguageServerLogs]);
+
+pub fn init(cx: &mut AppContext) {
+ 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 LogStore {
+ fn new(cx: &mut ModelContext<Self>) -> Self {
+ let (io_tx, mut io_rx) = mpsc::unbounded();
+ let this = Self {
+ projects: HashMap::default(),
+ io_tx,
+ };
+ cx.spawn_weak(|this, mut cx| async move {
+ while let Some((project, server_id, is_output, mut message)) = io_rx.next().await {
+ if let Some(this) = this.upgrade(&cx) {
+ this.update(&mut cx, |this, cx| {
+ message.push('\n');
+ this.on_io(project, server_id, is_output, &message, cx);
+ });
+ }
+ }
+ anyhow::Ok(())
+ })
+ .detach();
+ this
+ }
+
+ pub fn has_enabled_logs_for_language_server(
+ &self,
+ project: &ModelHandle<Project>,
+ server_id: LanguageServerId,
+ ) -> bool {
+ self.projects
+ .get(&project.downgrade())
+ .map_or(false, |store| store.servers.contains_key(&server_id))
+ }
+
+ pub fn enable_logs_for_language_server(
+ &mut self,
+ project: &ModelHandle<Project>,
+ 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, _, _| {
+ 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,
+ _: &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>,
+ ) {
+ self.log_store.update(cx, |log_store, cx| {
+ if enabled {
+ log_store.enable_logs_for_language_server(&self.project, server_id, cx);
+ } else {
+ log_store.disable_logs_for_language_server(&self.project, server_id, cx);
+ }
+ });
+ }
+}
+
+impl View for LspLogView {
+ fn ui_name() -> &'static str {
+ "LspLogView"
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ if let Some(editor) = &self.editor {
+ ChildView::new(&editor, cx).into_any()
+ } else {
+ Empty::new().into_any()
+ }
+ }
+}
+
+impl Item for LspLogView {
+ fn tab_content<V: View>(
+ &self,
+ _: Option<usize>,
+ style: &theme::Tab,
+ _: &AppContext,
+ ) -> AnyElement<V> {
+ Label::new("LSP Logs", style.label.clone()).into_any()
+ }
+}
+
+impl ToolbarItemView for LspLogToolbarItemView {
+ fn set_active_pane_item(
+ &mut self,
+ active_pane_item: Option<&dyn ItemHandle>,
+ _: &mut ViewContext<Self>,
+ ) -> workspace::ToolbarItemLocation {
+ self.menu_open = false;
+ if let Some(item) = active_pane_item {
+ if let Some(log_view) = item.downcast::<LspLogView>() {
+ self.log_view = Some(log_view.clone());
+ return ToolbarItemLocation::PrimaryLeft {
+ flex: Some((1., false)),
+ };
+ }
+ }
+ self.log_view = None;
+ ToolbarItemLocation::Hidden
+ }
+}
+
+impl View for LspLogToolbarItemView {
+ fn ui_name() -> &'static str {
+ "LspLogView"
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ let theme = cx.global::<Settings>().theme.clone();
+ let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() };
+ let project = self.project.read(cx);
+ let log_view = log_view.read(cx);
+ let log_store = log_view.log_store.read(cx);
+
+ 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| {
+ if let Ok(ix) = language_servers.binary_search_by_key(¤t_server_id, |e| e.0) {
+ Some(language_servers[ix].clone())
+ } else {
+ None
+ }
+ });
+
+ enum Menu {}
+
+ Stack::new()
+ .with_child(Self::render_language_server_menu_header(
+ current_server,
+ &self.project,
+ &theme,
+ cx,
+ ))
+ .with_children(if self.menu_open {
+ Some(
+ Overlay::new(
+ MouseEventHandler::<Menu, _>::new(0, cx, move |_, cx| {
+ Flex::column()
+ .with_children(language_servers.into_iter().filter_map(
+ |(id, name, worktree_id, logging_enabled)| {
+ Self::render_language_server_menu_item(
+ id,
+ name,
+ worktree_id,
+ logging_enabled,
+ Some(id) == current_server_id,
+ &self.project,
+ &theme,
+ cx,
+ )
+ },
+ ))
+ .contained()
+ .with_style(theme.context_menu.container)
+ .constrained()
+ .with_width(400.)
+ .with_height(400.)
+ })
+ .on_down_out(MouseButton::Left, |_, this, cx| {
+ this.menu_open = false;
+ cx.notify()
+ }),
+ )
+ .with_fit_mode(OverlayFitMode::SwitchAnchor)
+ .with_anchor_corner(AnchorCorner::TopLeft)
+ .with_z_index(999)
+ .aligned()
+ .bottom()
+ .left(),
+ )
+ } else {
+ None
+ })
+ .aligned()
+ .left()
+ .clipped()
+ .into_any()
+ }
+}
+
+impl LspLogToolbarItemView {
+ pub fn new(project: ModelHandle<Project>) -> Self {
+ Self {
+ menu_open: false,
+ log_view: None,
+ project,
+ }
+ }
+
+ fn toggle_menu(&mut self, cx: &mut ViewContext<Self>) {
+ self.menu_open = !self.menu_open;
+ cx.notify();
+ }
+
+ fn toggle_logging_for_server(
+ &mut self,
+ id: LanguageServerId,
+ enabled: bool,
+ cx: &mut ViewContext<Self>,
+ ) {
+ if let Some(log_view) = &self.log_view {
+ log_view.update(cx, |log_view, 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;
+ log_view.editor = None;
+ cx.notify();
+ }
+ });
+ }
+ 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;
+ }
+ cx.notify();
+ }
+
+ fn render_language_server_menu_header(
+ current_server: Option<(LanguageServerId, LanguageServerName, WorktreeId, bool)>,
+ project: &ModelHandle<Project>,
+ theme: &Arc<Theme>,
+ cx: &mut ViewContext<Self>,
+ ) -> impl Element<Self> {
+ enum ToggleMenu {}
+ MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, cx| {
+ let project = project.read(cx);
+ let label: Cow<str> = current_server
+ .and_then(|(_, server_name, worktree_id, _)| {
+ let worktree = project.worktree_for_id(worktree_id, cx)?;
+ let worktree = &worktree.read(cx);
+ Some(format!("{} - ({})", server_name.0, worktree.root_name()).into())
+ })
+ .unwrap_or_else(|| "No server selected".into());
+ Label::new(
+ label,
+ theme
+ .context_menu
+ .item
+ .style_for(state, false)
+ .label
+ .clone(),
+ )
+ })
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, move |_, view, cx| {
+ view.toggle_menu(cx);
+ })
+ }
+
+ fn render_language_server_menu_item(
+ id: LanguageServerId,
+ name: LanguageServerName,
+ worktree_id: WorktreeId,
+ logging_enabled: bool,
+ is_selected: bool,
+ project: &ModelHandle<Project>,
+ theme: &Arc<Theme>,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<impl Element<Self>> {
+ enum ActivateLog {}
+ let project = project.read(cx);
+ let worktree = project.worktree_for_id(worktree_id, cx)?;
+ let worktree = &worktree.read(cx);
+ if !worktree.is_visible() {
+ return None;
+ }
+ let label = format!("{} - ({})", name.0, worktree.root_name());
+
+ Some(
+ MouseEventHandler::<ActivateLog, _>::new(id.0, cx, move |state, cx| {
+ 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()).aligned().left())
+ .align_children_center()
+ .contained()
+ .with_style(item_style.container)
+ })
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, move |_, view, cx| {
+ view.show_logs_for_server(id, cx);
+ }),
+ )
+ }
+}
+
+impl Entity for LogStore {
+ type Event = ();
+}
+
+impl Entity for LspLogView {
+ type Event = ();
+}
+
+impl Entity for LspLogToolbarItemView {
+ type Event = ();
+}
@@ -13,7 +13,7 @@ gpui = { path = "../gpui" }
util = { path = "../util" }
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
async-tar = "0.4.2"
-futures = "0.3"
+futures = { workspace = true }
anyhow = "1.0.38"
parking_lot = "0.11.1"
serde = { workspace = true }
@@ -41,7 +41,7 @@ aho-corasick = "0.7"
anyhow = "1.0.57"
async-trait = "0.1"
backtrace = "0.3"
-futures = "0.3"
+futures = { workspace = true }
ignore = "0.4"
lazy_static = "1.4.0"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
@@ -185,6 +185,8 @@ pub struct Collaborator {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event {
+ LanguageServerAdded(LanguageServerId),
+ LanguageServerRemoved(LanguageServerId),
ActiveEntryChanged(Option<ProjectEntryId>),
WorktreeAdded,
WorktreeRemoved(WorktreeId),
@@ -1869,7 +1871,7 @@ impl Project {
let next_snapshot = buffer.text_snapshot();
let language_servers: Vec<_> = self
- .language_servers_iter_for_buffer(buffer, cx)
+ .language_servers_for_buffer(buffer, cx)
.map(|i| i.1.clone())
.collect();
@@ -6279,7 +6281,25 @@ impl Project {
}
}
- pub fn language_servers_iter_for_buffer(
+ pub fn language_servers(
+ &self,
+ ) -> impl '_ + Iterator<Item = (LanguageServerId, LanguageServerName, WorktreeId)> {
+ self.language_server_ids
+ .iter()
+ .map(|((worktree_id, server_name), server_id)| {
+ (*server_id, server_name.clone(), *worktree_id)
+ })
+ }
+
+ pub fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
+ if let LanguageServerState::Running { server, .. } = self.language_servers.get(&id)? {
+ Some(server.clone())
+ } else {
+ None
+ }
+ }
+
+ pub fn language_servers_for_buffer(
&self,
buffer: &Buffer,
cx: &AppContext,
@@ -6299,20 +6319,12 @@ impl Project {
})
}
- fn language_servers_for_buffer(
- &self,
- buffer: &Buffer,
- cx: &AppContext,
- ) -> Vec<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
- self.language_servers_iter_for_buffer(buffer, cx).collect()
- }
-
fn primary_language_servers_for_buffer(
&self,
buffer: &Buffer,
cx: &AppContext,
) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
- self.language_servers_iter_for_buffer(buffer, cx).next()
+ self.language_servers_for_buffer(buffer, cx).next()
}
fn language_server_for_buffer(
@@ -6321,7 +6333,7 @@ impl Project {
server_id: LanguageServerId,
cx: &AppContext,
) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
- self.language_servers_iter_for_buffer(buffer, cx)
+ self.language_servers_for_buffer(buffer, cx)
.find(|(_, s)| s.server_id() == server_id)
}
@@ -20,7 +20,7 @@ theme = { path = "../theme" }
util = { path = "../util" }
workspace = { path = "../workspace" }
postage = { workspace = true }
-futures = "0.3"
+futures = { workspace = true }
unicase = "2.6"
[dev-dependencies]
@@ -24,7 +24,7 @@ postage = { workspace = true }
smol = "1.2"
[dev-dependencies]
-futures = "0.3"
+futures = { workspace = true }
settings = { path = "../settings", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
@@ -21,7 +21,7 @@ anyhow = "1.0"
async-lock = "2.4"
async-tungstenite = "0.16"
base64 = "0.13"
-futures = "0.3"
+futures = { workspace = true }
parking_lot = "0.11.1"
prost = "0.8"
rand = "0.8"
@@ -20,7 +20,7 @@ theme = { path = "../theme" }
util = { path = "../util" }
workspace = { path = "../workspace" }
anyhow = "1.0"
-futures = "0.3"
+futures = { workspace = true }
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
postage = { workspace = true }
serde = { workspace = true }
@@ -18,7 +18,7 @@ gpui = { path = "../gpui" }
sqlez = { path = "../sqlez" }
fs = { path = "../fs" }
anyhow = "1.0.38"
-futures = "0.3"
+futures = { workspace = true }
theme = { path = "../theme" }
staff_mode = { path = "../staff_mode" }
util = { path = "../util" }
@@ -14,5 +14,5 @@ smol = "1.2"
thread_local = "1.1.4"
lazy_static = "1.4"
parking_lot = "0.11.1"
-futures = "0.3"
-uuid = { version = "1.1.2", features = ["v4"] }
+futures = { workspace = true }
+uuid = { version = "1.1.2", features = ["v4"] }
@@ -20,7 +20,7 @@ procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f
smallvec = { workspace = true }
smol = "1.2.5"
mio-extras = "2.0.6"
-futures = "0.3"
+futures = { workspace = true }
ordered-float = "2.1.1"
itertools = "0.10"
dirs = "4.0.0"
@@ -24,7 +24,7 @@ terminal = { path = "../terminal" }
smallvec = { workspace = true }
smol = "1.2.5"
mio-extras = "2.0.6"
-futures = "0.3"
+futures = { workspace = true }
ordered-float = "2.1.1"
itertools = "0.10"
dirs = "4.0.0"
@@ -27,28 +27,40 @@ pub struct CheckboxStyle {
pub hovered_and_checked: ContainerStyle,
}
-pub fn checkbox<Tag: 'static, V: View>(
+pub fn checkbox<Tag, V, F>(
label: &'static str,
style: &CheckboxStyle,
checked: bool,
+ id: usize,
cx: &mut ViewContext<V>,
- change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
-) -> MouseEventHandler<Tag, V> {
+ change: F,
+) -> 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())
.contained()
.with_style(style.label.container);
-
- checkbox_with_label(label, style, checked, cx, change)
+ checkbox_with_label(label, style, checked, id, 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,
style: &CheckboxStyle,
checked: bool,
+ id: usize,
cx: &mut ViewContext<V>,
- change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
-) -> MouseEventHandler<Tag, V> {
- MouseEventHandler::new(0, cx, |state, _| {
+ change: F,
+) -> MouseEventHandler<Tag, V>
+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 {
svg(&style.icon)
} else {
@@ -75,8 +87,8 @@ pub fn checkbox_with_label<Tag: 'static, D: Element<V>, V: View>(
.with_child(label)
.align_children_center()
})
- .on_click(platform::MouseButton::Left, move |_, _, cx| {
- change(!checked, cx)
+ .on_click(platform::MouseButton::Left, move |_, view, cx| {
+ change(view, !checked, cx)
})
.with_cursor_style(platform::CursorStyle::PointingHand)
}
@@ -16,7 +16,7 @@ anyhow = "1.0.38"
backtrace = "0.3"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
lazy_static = "1.4.0"
-futures = "0.3"
+futures = { workspace = true }
isahc = "1.7"
smol = "1.2.5"
url = "2.2"
@@ -126,7 +126,7 @@ impl View for WelcomePage {
.with_child(
Flex::column()
.with_child(
- theme::ui::checkbox_with_label::<Metrics, _, Self>(
+ theme::ui::checkbox_with_label::<Metrics, _, Self, _>(
Flex::column()
.with_child(
Label::new(
@@ -146,8 +146,9 @@ impl View for WelcomePage {
),
&theme.welcome.checkbox,
metrics,
+ 0,
cx,
- |checked, cx| {
+ |_, checked, cx| {
SettingsFile::update(cx, move |file| {
file.telemetry.set_metrics(checked)
})
@@ -157,12 +158,13 @@ impl View for WelcomePage {
.with_style(theme.welcome.checkbox_container),
)
.with_child(
- theme::ui::checkbox::<Diagnostics, Self>(
+ theme::ui::checkbox::<Diagnostics, Self, _>(
"Send crash reports",
&theme.welcome.checkbox,
diagnostics,
+ 0,
cx,
- |checked, cx| {
+ |_, checked, cx| {
SettingsFile::update(cx, move |file| {
file.telemetry.set_diagnostics(checked)
})
@@ -38,7 +38,7 @@ util = { path = "../util" }
async-recursion = "1.0.0"
bincode = "1.2.1"
anyhow = "1.0.38"
-futures = "0.3"
+futures = { workspace = true }
lazy_static = "1.4"
env_logger = "0.9.1"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
@@ -46,6 +46,7 @@ journal = { path = "../journal" }
language = { path = "../language" }
language_selector = { path = "../language_selector" }
lsp = { path = "../lsp" }
+lsp_log = { path = "../lsp_log" }
node_runtime = { path = "../node_runtime" }
outline = { path = "../outline" }
plugin_runtime = { path = "../plugin_runtime" }
@@ -76,7 +77,7 @@ chrono = "0.4"
ctor = "0.1.20"
easy-parallel = "3.1.0"
env_logger = "0.9"
-futures = "0.3"
+futures = { workspace = true }
ignore = "0.4"
image = "0.23"
indexmap = "1.6.2"
@@ -109,7 +110,7 @@ tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "05e3631c6a0701c1fa518b0fee7be95a2ceef5e2" }
tree-sitter-embedded-template = "0.20.0"
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
-tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8" }
+tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
tree-sitter-rust = "0.20.3"
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
tree-sitter-python = "0.20.2"
@@ -262,6 +262,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
);
activity_indicator::init(cx);
copilot_button::init(cx);
+ lsp_log::init(cx);
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
settings::KeymapFileContent::load_defaults(cx);
}
@@ -273,7 +274,7 @@ pub fn initialize_workspace(
) {
let workspace_handle = cx.handle();
cx.subscribe(&workspace_handle, {
- move |_, _, event, cx| {
+ move |workspace, _, event, cx| {
if let workspace::Event::PaneAdded(pane) = event {
pane.update(cx, |pane, cx| {
pane.toolbar().update(cx, |toolbar, cx| {
@@ -287,6 +288,10 @@ pub fn initialize_workspace(
toolbar.add_item(submit_feedback_button, cx);
let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
toolbar.add_item(feedback_info_text, cx);
+ let lsp_log_item = cx.add_view(|_| {
+ lsp_log::LspLogToolbarItemView::new(workspace.project().clone())
+ });
+ toolbar.add_item(lsp_log_item, cx);
})
});
}