use std::iter::repeat; use std::path::Path; use std::process::Stdio; use std::sync::Arc; use Default::default; use Into::into; use anyhow::{anyhow, bail}; use dsb::Cell; use dsb::cell::Style; use lsp_types::*; use rust_analyzer::lsp::ext::*; use crate::FG; use crate::edi::{Editor, lsp_m}; use crate::lsp::{Anonymize, PathURI}; use crate::menu::charc; use crate::menu::generic::{GenericMenu, MenuData}; use crate::text::{RopeExt, SortTedits, col, color_}; macro_rules! commands { ($(#[doc = $d: literal] $t:tt $identifier: ident: $c:literal),+ $(,)?) => { #[derive(Copy, Clone, PartialEq, Eq)] pub enum Cmd { $(#[doc = $d] $identifier),+ } impl Cmd { pub const ALL: [Cmd; { [$($c),+].len() }] = [$(Self::$identifier,)+]; pub fn name(self) -> &'static str { match self { $(Self::$identifier => $c,)+ } } pub fn desc(self) -> &'static str { match self { $(Self::$identifier => $d,)+ } } pub fn needs_lsp(self) -> bool { match self { $(Self::$identifier => stringify!($t) == "@",)+ } } } }; } commands!( /// move item at cursor down @ RAMoveID: "move-item-down", /// move item at cursor up @ RAMoveIU: "move-item-up", /// restart rust analyzer @ RARestart: "ra-restart", /// go to parent module @ RAParent: "parent", /// join lines under cursors. @ RAJoinLines: "join-lines", /// gets list of runnables @ RARunnables: "runnables", /// Open docs for type at cursor @ RADocs: "open-docs", /// Rebuilds rust-analyzer proc macros @ RARebuildProcMacros: "rebuild-proc-macros", /// Cancels current running rust-analyzer check process @ RACancelFlycheck: "cancel-flycheck", /// Opens Cargo.toml file for this workspace @ RAOpenCargoToml: "open-cargo-toml", /// Runs the test at the cursor @ RARunTest: "run-test", ); pub enum Cmds {} impl MenuData for Cmds { const HEIGHT: usize = 30; type Data = (); type Element<'a> = Cmd; fn gn((): &()) -> impl Iterator { Cmd::ALL.into_iter() } fn r( _: &Self::Data, x: Cmd, _: &Path, c: usize, selected: bool, indices: &[u32], to: &mut Vec, ) { let bg = if selected { col!("#262d3b") } else { col!("#1c212b") }; let ds: Style = Style::new(FG, bg); let d: Cell = Cell { letter: None, style: ds }; let mut b = vec![d; c]; let (bgt, col, ty) = (col!("#FFFFFF"), col!("#ACACAC"), ""); b.iter_mut().zip(ty.chars()).for_each(|(x, c)| { *x = (Style::new(col, bgt) | Style::BOLD).basic(c) }); let i = &mut b[..]; let qualifier = x.desc().chars(); let _left = i.len() as i32 - (charc(&x.name()) as i32 + qualifier.clone().count() as i32) - 3; i.iter_mut() .zip( x.name() .chars() .chain([' ']) .map(|x| ds.basic(x)) .zip(0..) .chain( qualifier .map(|x| { Style { bg, fg: color_("#858685"), ..default() } .basic(x) }) .zip(repeat(u32::MAX)), ), ) .for_each(|(a, (b, i))| { *a = b; if indices.contains(&i) { a.style |= (Style::BOLD, color_("#ffcc66")); } }); to.extend(b); } } pub type Commands = GenericMenu; impl<'a> crate::menu::Key<'a> for Cmd { fn key(&self) -> impl Into> { self.name() } } impl Editor { pub fn handle_command( &mut self, z: Cmd, w: Arc, ) -> anyhow::Result<()> { if !z.needs_lsp() { return Ok(()); } let Some((l, o)) = lsp_m!(self + p) else { bail!("no lsp"); }; match z { Cmd::RAMoveIU | Cmd::RAMoveID => { let r = self .text .to_l_position(*self.text.cursor.first()) .unwrap(); let mut x = l.request_immediate::(&MoveItemParams { direction: if let Cmd::RAMoveIU = z { MoveItemDirection::Up } else { MoveItemDirection::Down }, text_document: o.tid(), range: Range { start : r, end : r}, })?; x.sort_tedits(); for t in x { self.text.apply_snippet_tedit(&t)?; } } Cmd::RARestart => { _ = l.request::(&())?.0; } Cmd::RAParent => { let Some(GotoDefinitionResponse::Link([ref x])) = l.request_immediate::( &TextDocumentPositionParams { text_document: o.tid(), position: self .text .to_l_position(*self.text.cursor.first()) .unwrap(), }, )? else { self.bar.last_action = "no such parent".into(); return Ok(()); }; self.open_loclink(x, w); } Cmd::RAJoinLines => { let teds = l.request_immediate::(&JoinLinesParams { ranges: self .text .cursor .iter() .filter_map(|x| { self.text.to_l_range( x.sel.map(into).unwrap_or(*x..*x), ) }) .collect(), text_document: o.tid(), })?; self.text .apply_tedits(&mut { teds }) .map_err(|_| anyhow!("couldnt apply edits"))?; } Cmd::RADocs => { let u = l.request_immediate::( &TextDocumentPositionParams { position: self .text .to_l_position(*self.text.cursor.first()) .unwrap(), text_document: o.tid(), }, )?; use rust_analyzer::lsp::ext::ExternalDocsResponse::*; std::process::Command::new("firefox-nightly") .arg( match &u { WithLocal(ExternalDocsPair { web: Some(x), .. }) if let Some("doc.rust-lang.org") = x.domain() && let Some(x) = x.path().strip_prefix("/nightly/") && option_env!("USER") == Some("os") => format!("http://127.0.0.1:3242/{x}"), // i have a lighttpd server running WithLocal(ExternalDocsPair { local: Some(url), .. }) if let Ok(p) = url.to_file_path() && p.exists() => url.to_string(), WithLocal(ExternalDocsPair { web: Some(url), .. }) | Simple(Some(url)) => url.to_string(), _ => return Ok(()), } .to_string(), ) .stdout(Stdio::null()) .spawn() .unwrap(); } Cmd::RARebuildProcMacros => { _ = l.request::(&())?; } Cmd::RACancelFlycheck => l.notify::(&())?, Cmd::RAOpenCargoToml => { let Some(GotoDefinitionResponse::Scalar(x)) = &l.request_immediate::( &OpenCargoTomlParams { text_document: o.tid() }, )? else { bail!("wtf?"); }; self.open_loc(x, w); } Cmd::RARunnables => { let x = l .runtime .block_on(l.runnables( o, self.text.to_l_position(*self.text.cursor.first()), )?) .anonymize()?; // self.state = State::Runnables; } _ => unimplemented!(), } Ok(()) } }