Merge pull request #2407 from zed-industries/lsp-log-view
Add a simple language server log view
This commit is contained in:
commit
d2ba1ec275
39 changed files with 780 additions and 92 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -3612,6 +3612,26 @@ dependencies = [
|
||||||
"url",
|
"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]]
|
[[package]]
|
||||||
name = "mach"
|
name = "mach"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -7239,7 +7259,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tree-sitter-json"
|
name = "tree-sitter-json"
|
||||||
version = "0.20.0"
|
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 = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
|
@ -8571,6 +8591,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"lsp",
|
"lsp",
|
||||||
|
"lsp_log",
|
||||||
"node_runtime",
|
"node_runtime",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"outline",
|
"outline",
|
||||||
|
|
|
@ -35,6 +35,7 @@ members = [
|
||||||
"crates/live_kit_client",
|
"crates/live_kit_client",
|
||||||
"crates/live_kit_server",
|
"crates/live_kit_server",
|
||||||
"crates/lsp",
|
"crates/lsp",
|
||||||
|
"crates/lsp_log",
|
||||||
"crates/media",
|
"crates/media",
|
||||||
"crates/menu",
|
"crates/menu",
|
||||||
"crates/node_runtime",
|
"crates/node_runtime",
|
||||||
|
@ -77,6 +78,7 @@ serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
||||||
rand = { version = "0.8" }
|
rand = { version = "0.8" }
|
||||||
postage = { version = "0.5", features = ["futures-traits"] }
|
postage = { version = "0.5", features = ["futures-traits"] }
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { version = "1.6", features = ["union"] }
|
||||||
|
futures = { version = "0.3" }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" }
|
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" }
|
||||||
|
|
|
@ -17,5 +17,5 @@ project = { path = "../project" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
smallvec = { workspace = true }
|
smallvec = { workspace = true }
|
||||||
|
|
|
@ -33,7 +33,7 @@ util = { path = "../util" }
|
||||||
|
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
async-broadcast = "0.4"
|
async-broadcast = "0.4"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
postage = { workspace = true }
|
postage = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -22,7 +22,7 @@ sum_tree = { path = "../sum_tree" }
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
async-recursion = "0.3"
|
async-recursion = "0.3"
|
||||||
async-tungstenite = { version = "0.16", features = ["async-tls"] }
|
async-tungstenite = { version = "0.16", features = ["async-tls"] }
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
image = "0.23"
|
image = "0.23"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
|
|
|
@ -27,7 +27,7 @@ base64 = "0.13"
|
||||||
clap = { version = "3.1", features = ["derive"], optional = true }
|
clap = { version = "3.1", features = ["derive"], optional = true }
|
||||||
dashmap = "5.4"
|
dashmap = "5.4"
|
||||||
envy = "0.4.2"
|
envy = "0.4.2"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
hyper = "0.14"
|
hyper = "0.14"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
lipsum = { version = "0.8", optional = true }
|
lipsum = { version = "0.8", optional = true }
|
||||||
|
|
|
@ -40,7 +40,7 @@ theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
postage = { workspace = true }
|
postage = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|
|
@ -35,7 +35,7 @@ log = "0.4"
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_derive = { workspace = true }
|
serde_derive = { workspace = true }
|
||||||
smol = "1.2.5"
|
smol = "1.2.5"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
|
|
|
@ -19,4 +19,4 @@ util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
smol = "1.2.5"
|
smol = "1.2.5"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
|
|
|
@ -47,7 +47,7 @@ workspace = { path = "../workspace" }
|
||||||
|
|
||||||
aho-corasick = "0.7"
|
aho-corasick = "0.7"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
indoc = "1.0.4"
|
indoc = "1.0.4"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
|
|
@ -511,6 +511,7 @@ pub struct Editor {
|
||||||
workspace_id: Option<WorkspaceId>,
|
workspace_id: Option<WorkspaceId>,
|
||||||
keymap_context_layers: BTreeMap<TypeId, KeymapContext>,
|
keymap_context_layers: BTreeMap<TypeId, KeymapContext>,
|
||||||
input_enabled: bool,
|
input_enabled: bool,
|
||||||
|
read_only: bool,
|
||||||
leader_replica_id: Option<u16>,
|
leader_replica_id: Option<u16>,
|
||||||
remote_id: Option<ViewId>,
|
remote_id: Option<ViewId>,
|
||||||
hover_state: HoverState,
|
hover_state: HoverState,
|
||||||
|
@ -1283,6 +1284,7 @@ impl Editor {
|
||||||
workspace_id: None,
|
workspace_id: None,
|
||||||
keymap_context_layers: Default::default(),
|
keymap_context_layers: Default::default(),
|
||||||
input_enabled: true,
|
input_enabled: true,
|
||||||
|
read_only: false,
|
||||||
leader_replica_id: None,
|
leader_replica_id: None,
|
||||||
remote_id: None,
|
remote_id: None,
|
||||||
hover_state: Default::default(),
|
hover_state: Default::default(),
|
||||||
|
@ -1425,6 +1427,10 @@ impl Editor {
|
||||||
self.input_enabled = input_enabled;
|
self.input_enabled = input_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_read_only(&mut self, read_only: bool) {
|
||||||
|
self.read_only = read_only;
|
||||||
|
}
|
||||||
|
|
||||||
fn selections_did_change(
|
fn selections_did_change(
|
||||||
&mut self,
|
&mut self,
|
||||||
local: bool,
|
local: bool,
|
||||||
|
@ -1533,6 +1539,10 @@ impl Editor {
|
||||||
S: ToOffset,
|
S: ToOffset,
|
||||||
T: Into<Arc<str>>,
|
T: Into<Arc<str>>,
|
||||||
{
|
{
|
||||||
|
if self.read_only {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.buffer
|
self.buffer
|
||||||
.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
|
.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
|
||||||
}
|
}
|
||||||
|
@ -1543,6 +1553,10 @@ impl Editor {
|
||||||
S: ToOffset,
|
S: ToOffset,
|
||||||
T: Into<Arc<str>>,
|
T: Into<Arc<str>>,
|
||||||
{
|
{
|
||||||
|
if self.read_only {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.edit(edits, Some(AutoindentMode::EachLine), 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>) {
|
pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
||||||
let text: Arc<str> = text.into();
|
let text: Arc<str> = text.into();
|
||||||
|
|
||||||
|
if self.read_only {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if !self.input_enabled {
|
if !self.input_enabled {
|
||||||
cx.emit(Event::InputIgnored { text });
|
cx.emit(Event::InputIgnored { text });
|
||||||
return;
|
return;
|
||||||
|
@ -2282,6 +2299,10 @@ impl Editor {
|
||||||
autoindent_mode: Option<AutoindentMode>,
|
autoindent_mode: Option<AutoindentMode>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
if self.read_only {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let text: Arc<str> = text.into();
|
let text: Arc<str> = text.into();
|
||||||
self.transact(cx, |this, cx| {
|
self.transact(cx, |this, cx| {
|
||||||
let old_selections = this.selections.all_adjusted(cx);
|
let old_selections = this.selections.all_adjusted(cx);
|
||||||
|
|
|
@ -16,7 +16,7 @@ client = { path = "../client" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
human_bytes = "0.4.1"
|
human_bytes = "0.4.1"
|
||||||
isahc = "1.7"
|
isahc = "1.7"
|
||||||
|
|
|
@ -15,7 +15,7 @@ rope = { path = "../rope" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
anyhow = "1.0.57"
|
anyhow = "1.0.57"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
fsevent = { path = "../fsevent" }
|
fsevent = { path = "../fsevent" }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
|
|
@ -19,7 +19,7 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
smol = "1.2"
|
smol = "1.2"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
git2 = { version = "0.15", default-features = false }
|
git2 = { version = "0.15", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -25,7 +25,7 @@ ctor = "0.1"
|
||||||
dhat = { version = "0.3", optional = true }
|
dhat = { version = "0.3", optional = true }
|
||||||
env_logger = { version = "0.9", optional = true }
|
env_logger = { version = "0.9", optional = true }
|
||||||
etagere = "0.2"
|
etagere = "0.2"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
image = "0.23"
|
image = "0.23"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
|
|
@ -39,7 +39,7 @@ util = { path = "../util" }
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
async-broadcast = "0.4"
|
async-broadcast = "0.4"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
|
|
|
@ -32,7 +32,7 @@ anyhow = "1.0.38"
|
||||||
async-broadcast = "0.4"
|
async-broadcast = "0.4"
|
||||||
core-foundation = "0.9.3"
|
core-foundation = "0.9.3"
|
||||||
core-graphics = "0.22.3"
|
core-graphics = "0.22.3"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
postage = { workspace = true }
|
postage = { workspace = true }
|
||||||
|
@ -56,7 +56,7 @@ cocoa = "0.24"
|
||||||
core-foundation = "0.9.3"
|
core-foundation = "0.9.3"
|
||||||
core-graphics = "0.22.3"
|
core-graphics = "0.22.3"
|
||||||
foreign-types = "0.3"
|
foreign-types = "0.3"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
hmac = "0.12"
|
hmac = "0.12"
|
||||||
jwt = "0.16"
|
jwt = "0.16"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
|
|
@ -12,7 +12,7 @@ doctest = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
hmac = "0.12"
|
hmac = "0.12"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
jwt = "0.16"
|
jwt = "0.16"
|
||||||
|
|
|
@ -17,7 +17,7 @@ gpui = { path = "../gpui" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true }
|
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"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
lsp-types = "0.91"
|
lsp-types = "0.91"
|
||||||
parking_lot = "0.11"
|
parking_lot = "0.11"
|
||||||
|
|
|
@ -20,10 +20,10 @@ use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
io::Write,
|
io::Write,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
str::FromStr,
|
str::{self, FromStr as _},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||||
Arc,
|
Arc, Weak,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::{path::Path, process::Stdio};
|
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 NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppContext)>;
|
||||||
type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
|
type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
|
||||||
|
type IoHandler = Box<dyn Send + FnMut(bool, &str)>;
|
||||||
|
|
||||||
pub struct LanguageServer {
|
pub struct LanguageServer {
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
next_id: AtomicUsize,
|
next_id: AtomicUsize,
|
||||||
outbound_tx: channel::Sender<Vec<u8>>,
|
outbound_tx: channel::Sender<String>,
|
||||||
name: String,
|
name: String,
|
||||||
capabilities: ServerCapabilities,
|
capabilities: ServerCapabilities,
|
||||||
code_action_kinds: Option<Vec<CodeActionKind>>,
|
code_action_kinds: Option<Vec<CodeActionKind>>,
|
||||||
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
||||||
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
|
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
|
||||||
|
io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
|
||||||
executor: Arc<executor::Background>,
|
executor: Arc<executor::Background>,
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
|
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
|
||||||
|
@ -56,9 +58,15 @@ pub struct LanguageServer {
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct LanguageServerId(pub usize);
|
pub struct LanguageServerId(pub usize);
|
||||||
|
|
||||||
pub struct Subscription {
|
pub enum Subscription {
|
||||||
method: &'static str,
|
Notification {
|
||||||
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -177,33 +185,40 @@ impl LanguageServer {
|
||||||
Stdout: AsyncRead + Unpin + Send + 'static,
|
Stdout: AsyncRead + Unpin + Send + 'static,
|
||||||
F: FnMut(AnyNotification) + 'static + Send,
|
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 =
|
let notification_handlers =
|
||||||
Arc::new(Mutex::new(HashMap::<_, NotificationHandler>::default()));
|
Arc::new(Mutex::new(HashMap::<_, NotificationHandler>::default()));
|
||||||
let response_handlers =
|
let response_handlers =
|
||||||
Arc::new(Mutex::new(Some(HashMap::<_, ResponseHandler>::default())));
|
Arc::new(Mutex::new(Some(HashMap::<_, ResponseHandler>::default())));
|
||||||
|
let io_handlers = Arc::new(Mutex::new(HashMap::default()));
|
||||||
let input_task = cx.spawn(|cx| {
|
let input_task = cx.spawn(|cx| {
|
||||||
let notification_handlers = notification_handlers.clone();
|
|
||||||
let response_handlers = response_handlers.clone();
|
|
||||||
Self::handle_input(
|
Self::handle_input(
|
||||||
stdout,
|
stdout,
|
||||||
on_unhandled_notification,
|
on_unhandled_notification,
|
||||||
notification_handlers,
|
notification_handlers.clone(),
|
||||||
response_handlers,
|
response_handlers.clone(),
|
||||||
|
io_handlers.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.log_err()
|
.log_err()
|
||||||
});
|
});
|
||||||
let (output_done_tx, output_done_rx) = barrier::channel();
|
|
||||||
let output_task = cx.background().spawn({
|
let output_task = cx.background().spawn({
|
||||||
let response_handlers = response_handlers.clone();
|
Self::handle_output(
|
||||||
Self::handle_output(stdin, outbound_rx, output_done_tx, response_handlers).log_err()
|
stdin,
|
||||||
|
outbound_rx,
|
||||||
|
output_done_tx,
|
||||||
|
response_handlers.clone(),
|
||||||
|
io_handlers.clone(),
|
||||||
|
)
|
||||||
|
.log_err()
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
server_id,
|
server_id,
|
||||||
notification_handlers,
|
notification_handlers,
|
||||||
response_handlers,
|
response_handlers,
|
||||||
|
io_handlers,
|
||||||
name: Default::default(),
|
name: Default::default(),
|
||||||
capabilities: Default::default(),
|
capabilities: Default::default(),
|
||||||
code_action_kinds,
|
code_action_kinds,
|
||||||
|
@ -226,6 +241,7 @@ impl LanguageServer {
|
||||||
mut on_unhandled_notification: F,
|
mut on_unhandled_notification: F,
|
||||||
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
||||||
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
|
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
|
||||||
|
io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> anyhow::Result<()>
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
|
@ -252,7 +268,13 @@ impl LanguageServer {
|
||||||
|
|
||||||
buffer.resize(message_len, 0);
|
buffer.resize(message_len, 0);
|
||||||
stdout.read_exact(&mut buffer).await?;
|
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 Ok(msg) = serde_json::from_slice::<AnyNotification>(&buffer) {
|
||||||
if let Some(handler) = notification_handlers.lock().get_mut(msg.method) {
|
if let Some(handler) = notification_handlers.lock().get_mut(msg.method) {
|
||||||
|
@ -291,9 +313,10 @@ impl LanguageServer {
|
||||||
|
|
||||||
async fn handle_output<Stdin>(
|
async fn handle_output<Stdin>(
|
||||||
stdin: Stdin,
|
stdin: Stdin,
|
||||||
outbound_rx: channel::Receiver<Vec<u8>>,
|
outbound_rx: channel::Receiver<String>,
|
||||||
output_done_tx: barrier::Sender,
|
output_done_tx: barrier::Sender,
|
||||||
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
|
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
|
||||||
|
io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
|
||||||
) -> anyhow::Result<()>
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
Stdin: AsyncWrite + Unpin + Send + 'static,
|
Stdin: AsyncWrite + Unpin + Send + 'static,
|
||||||
|
@ -307,13 +330,17 @@ impl LanguageServer {
|
||||||
});
|
});
|
||||||
let mut content_len_buffer = Vec::new();
|
let mut content_len_buffer = Vec::new();
|
||||||
while let Ok(message) = outbound_rx.recv().await {
|
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();
|
content_len_buffer.clear();
|
||||||
write!(content_len_buffer, "{}", message.len()).unwrap();
|
write!(content_len_buffer, "{}", message.len()).unwrap();
|
||||||
stdin.write_all(CONTENT_LEN_HEADER.as_bytes()).await?;
|
stdin.write_all(CONTENT_LEN_HEADER.as_bytes()).await?;
|
||||||
stdin.write_all(&content_len_buffer).await?;
|
stdin.write_all(&content_len_buffer).await?;
|
||||||
stdin.write_all("\r\n\r\n".as_bytes()).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?;
|
stdin.flush().await?;
|
||||||
}
|
}
|
||||||
drop(output_done_tx);
|
drop(output_done_tx);
|
||||||
|
@ -464,6 +491,19 @@ impl LanguageServer {
|
||||||
self.on_custom_request(T::METHOD, f)
|
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) {
|
pub fn remove_request_handler<T: request::Request>(&self) {
|
||||||
self.notification_handlers.lock().remove(T::METHOD);
|
self.notification_handlers.lock().remove(T::METHOD);
|
||||||
}
|
}
|
||||||
|
@ -490,9 +530,9 @@ impl LanguageServer {
|
||||||
prev_handler.is_none(),
|
prev_handler.is_none(),
|
||||||
"registered multiple handlers for the same LSP method"
|
"registered multiple handlers for the same LSP method"
|
||||||
);
|
);
|
||||||
Subscription {
|
Subscription::Notification {
|
||||||
method,
|
method,
|
||||||
notification_handlers: self.notification_handlers.clone(),
|
notification_handlers: Some(self.notification_handlers.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,7 +577,7 @@ impl LanguageServer {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if let Some(response) =
|
if let Some(response) =
|
||||||
serde_json::to_vec(&response).log_err()
|
serde_json::to_string(&response).log_err()
|
||||||
{
|
{
|
||||||
outbound_tx.try_send(response).ok();
|
outbound_tx.try_send(response).ok();
|
||||||
}
|
}
|
||||||
|
@ -560,7 +600,7 @@ impl LanguageServer {
|
||||||
message: error.to_string(),
|
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();
|
outbound_tx.try_send(response).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -572,9 +612,9 @@ impl LanguageServer {
|
||||||
prev_handler.is_none(),
|
prev_handler.is_none(),
|
||||||
"registered multiple handlers for the same LSP method"
|
"registered multiple handlers for the same LSP method"
|
||||||
);
|
);
|
||||||
Subscription {
|
Subscription::Notification {
|
||||||
method,
|
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>(
|
fn request_internal<T: request::Request>(
|
||||||
next_id: &AtomicUsize,
|
next_id: &AtomicUsize,
|
||||||
response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
|
response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
|
||||||
outbound_tx: &channel::Sender<Vec<u8>>,
|
outbound_tx: &channel::Sender<String>,
|
||||||
params: T::Params,
|
params: T::Params,
|
||||||
) -> impl 'static + Future<Output = Result<T::Result>>
|
) -> impl 'static + Future<Output = Result<T::Result>>
|
||||||
where
|
where
|
||||||
T::Result: 'static + Send,
|
T::Result: 'static + Send,
|
||||||
{
|
{
|
||||||
let id = next_id.fetch_add(1, SeqCst);
|
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,
|
jsonrpc: JSON_RPC_VERSION,
|
||||||
id,
|
id,
|
||||||
method: T::METHOD,
|
method: T::METHOD,
|
||||||
|
@ -662,10 +702,10 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn notify_internal<T: notification::Notification>(
|
fn notify_internal<T: notification::Notification>(
|
||||||
outbound_tx: &channel::Sender<Vec<u8>>,
|
outbound_tx: &channel::Sender<String>,
|
||||||
params: T::Params,
|
params: T::Params,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let message = serde_json::to_vec(&Notification {
|
let message = serde_json::to_string(&Notification {
|
||||||
jsonrpc: JSON_RPC_VERSION,
|
jsonrpc: JSON_RPC_VERSION,
|
||||||
method: T::METHOD,
|
method: T::METHOD,
|
||||||
params,
|
params,
|
||||||
|
@ -685,8 +725,14 @@ impl Drop for LanguageServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Subscription {
|
impl Subscription {
|
||||||
pub fn detach(mut self) {
|
pub fn detach(&mut self) {
|
||||||
self.method = "";
|
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 {
|
impl Drop for Subscription {
|
||||||
fn drop(&mut self) {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
29
crates/lsp_log/Cargo.toml
Normal file
29
crates/lsp_log/Cargo.toml
Normal file
|
@ -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"
|
523
crates/lsp_log/src/lsp_log.rs
Normal file
523
crates/lsp_log/src/lsp_log.rs
Normal file
|
@ -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" }
|
util = { path = "../util" }
|
||||||
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
|
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
|
||||||
async-tar = "0.4.2"
|
async-tar = "0.4.2"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|
|
@ -41,7 +41,7 @@ aho-corasick = "0.7"
|
||||||
anyhow = "1.0.57"
|
anyhow = "1.0.57"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
backtrace = "0.3"
|
backtrace = "0.3"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
ignore = "0.4"
|
ignore = "0.4"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
|
|
|
@ -185,6 +185,8 @@ pub struct Collaborator {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
LanguageServerAdded(LanguageServerId),
|
||||||
|
LanguageServerRemoved(LanguageServerId),
|
||||||
ActiveEntryChanged(Option<ProjectEntryId>),
|
ActiveEntryChanged(Option<ProjectEntryId>),
|
||||||
WorktreeAdded,
|
WorktreeAdded,
|
||||||
WorktreeRemoved(WorktreeId),
|
WorktreeRemoved(WorktreeId),
|
||||||
|
@ -1869,7 +1871,7 @@ impl Project {
|
||||||
let next_snapshot = buffer.text_snapshot();
|
let next_snapshot = buffer.text_snapshot();
|
||||||
|
|
||||||
let language_servers: Vec<_> = self
|
let language_servers: Vec<_> = self
|
||||||
.language_servers_iter_for_buffer(buffer, cx)
|
.language_servers_for_buffer(buffer, cx)
|
||||||
.map(|i| i.1.clone())
|
.map(|i| i.1.clone())
|
||||||
.collect();
|
.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,
|
&self,
|
||||||
buffer: &Buffer,
|
buffer: &Buffer,
|
||||||
cx: &AppContext,
|
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(
|
fn primary_language_servers_for_buffer(
|
||||||
&self,
|
&self,
|
||||||
buffer: &Buffer,
|
buffer: &Buffer,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
|
) -> 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(
|
fn language_server_for_buffer(
|
||||||
|
@ -6321,7 +6333,7 @@ impl Project {
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
|
) -> 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)
|
.find(|(_, s)| s.server_id() == server_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
postage = { workspace = true }
|
postage = { workspace = true }
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
unicase = "2.6"
|
unicase = "2.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -24,7 +24,7 @@ postage = { workspace = true }
|
||||||
smol = "1.2"
|
smol = "1.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
settings = { path = "../settings", features = ["test-support"] }
|
settings = { path = "../settings", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
language = { path = "../language", features = ["test-support"] }
|
language = { path = "../language", features = ["test-support"] }
|
||||||
|
|
|
@ -21,7 +21,7 @@ anyhow = "1.0"
|
||||||
async-lock = "2.4"
|
async-lock = "2.4"
|
||||||
async-tungstenite = "0.16"
|
async-tungstenite = "0.16"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
prost = "0.8"
|
prost = "0.8"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|
|
@ -20,7 +20,7 @@ theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
postage = { workspace = true }
|
postage = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|
|
@ -18,7 +18,7 @@ gpui = { path = "../gpui" }
|
||||||
sqlez = { path = "../sqlez" }
|
sqlez = { path = "../sqlez" }
|
||||||
fs = { path = "../fs" }
|
fs = { path = "../fs" }
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
staff_mode = { path = "../staff_mode" }
|
staff_mode = { path = "../staff_mode" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
|
|
@ -14,5 +14,5 @@ smol = "1.2"
|
||||||
thread_local = "1.1.4"
|
thread_local = "1.1.4"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
uuid = { version = "1.1.2", features = ["v4"] }
|
uuid = { version = "1.1.2", features = ["v4"] }
|
||||||
|
|
|
@ -20,7 +20,7 @@ procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f
|
||||||
smallvec = { workspace = true }
|
smallvec = { workspace = true }
|
||||||
smol = "1.2.5"
|
smol = "1.2.5"
|
||||||
mio-extras = "2.0.6"
|
mio-extras = "2.0.6"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
ordered-float = "2.1.1"
|
ordered-float = "2.1.1"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
dirs = "4.0.0"
|
dirs = "4.0.0"
|
||||||
|
|
|
@ -24,7 +24,7 @@ terminal = { path = "../terminal" }
|
||||||
smallvec = { workspace = true }
|
smallvec = { workspace = true }
|
||||||
smol = "1.2.5"
|
smol = "1.2.5"
|
||||||
mio-extras = "2.0.6"
|
mio-extras = "2.0.6"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
ordered-float = "2.1.1"
|
ordered-float = "2.1.1"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
dirs = "4.0.0"
|
dirs = "4.0.0"
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ anyhow = "1.0.38"
|
||||||
backtrace = "0.3"
|
backtrace = "0.3"
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
isahc = "1.7"
|
isahc = "1.7"
|
||||||
smol = "1.2.5"
|
smol = "1.2.5"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -38,7 +38,7 @@ util = { path = "../util" }
|
||||||
async-recursion = "1.0.0"
|
async-recursion = "1.0.0"
|
||||||
bincode = "1.2.1"
|
bincode = "1.2.1"
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
env_logger = "0.9.1"
|
env_logger = "0.9.1"
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
|
|
|
@ -46,6 +46,7 @@ journal = { path = "../journal" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
language_selector = { path = "../language_selector" }
|
language_selector = { path = "../language_selector" }
|
||||||
lsp = { path = "../lsp" }
|
lsp = { path = "../lsp" }
|
||||||
|
lsp_log = { path = "../lsp_log" }
|
||||||
node_runtime = { path = "../node_runtime" }
|
node_runtime = { path = "../node_runtime" }
|
||||||
outline = { path = "../outline" }
|
outline = { path = "../outline" }
|
||||||
plugin_runtime = { path = "../plugin_runtime" }
|
plugin_runtime = { path = "../plugin_runtime" }
|
||||||
|
@ -76,7 +77,7 @@ chrono = "0.4"
|
||||||
ctor = "0.1.20"
|
ctor = "0.1.20"
|
||||||
easy-parallel = "3.1.0"
|
easy-parallel = "3.1.0"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
futures = "0.3"
|
futures = { workspace = true }
|
||||||
ignore = "0.4"
|
ignore = "0.4"
|
||||||
image = "0.23"
|
image = "0.23"
|
||||||
indexmap = "1.6.2"
|
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-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "05e3631c6a0701c1fa518b0fee7be95a2ceef5e2" }
|
||||||
tree-sitter-embedded-template = "0.20.0"
|
tree-sitter-embedded-template = "0.20.0"
|
||||||
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
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-rust = "0.20.3"
|
||||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||||
tree-sitter-python = "0.20.2"
|
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);
|
activity_indicator::init(cx);
|
||||||
copilot_button::init(cx);
|
copilot_button::init(cx);
|
||||||
|
lsp_log::init(cx);
|
||||||
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
|
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
|
||||||
settings::KeymapFileContent::load_defaults(cx);
|
settings::KeymapFileContent::load_defaults(cx);
|
||||||
}
|
}
|
||||||
|
@ -273,7 +274,7 @@ pub fn initialize_workspace(
|
||||||
) {
|
) {
|
||||||
let workspace_handle = cx.handle();
|
let workspace_handle = cx.handle();
|
||||||
cx.subscribe(&workspace_handle, {
|
cx.subscribe(&workspace_handle, {
|
||||||
move |_, _, event, cx| {
|
move |workspace, _, event, cx| {
|
||||||
if let workspace::Event::PaneAdded(pane) = event {
|
if let workspace::Event::PaneAdded(pane) = event {
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
pane.toolbar().update(cx, |toolbar, cx| {
|
pane.toolbar().update(cx, |toolbar, cx| {
|
||||||
|
@ -287,6 +288,10 @@ pub fn initialize_workspace(
|
||||||
toolbar.add_item(submit_feedback_button, cx);
|
toolbar.add_item(submit_feedback_button, cx);
|
||||||
let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
|
let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
|
||||||
toolbar.add_item(feedback_info_text, cx);
|
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);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue