diff --git a/CHANGELOG.md b/CHANGELOG.md index 8624c7c03..bc0f48b71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## Added - Support generating the source of `tags` provider using the Rust binary, remove the vista.vim dep from `tags` provider. #795 +- Initial support of preview with context. #798 ## Fixed diff --git a/Cargo.lock b/Cargo.lock index 91b28fb5c..85bd6dad0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1985,6 +1985,7 @@ dependencies = [ "directories", "memchr", "simdutf8", + "types", ] [[package]] diff --git a/crates/maple_cli/src/command/ctags/buffer_tags.rs b/crates/maple_cli/src/command/ctags/buffer_tags.rs index 95ac8750a..e8b473fe6 100644 --- a/crates/maple_cli/src/command/ctags/buffer_tags.rs +++ b/crates/maple_cli/src/command/ctags/buffer_tags.rs @@ -1,7 +1,8 @@ use std::ops::Deref; +use std::path::Path; use std::sync::atomic::{AtomicUsize, Ordering}; -use anyhow::Result; +use anyhow::{Context, Result}; use clap::Parser; use filter::subprocess::{Exec, Redirection}; use itertools::Itertools; @@ -15,6 +16,10 @@ use crate::tools::ctags::CTAGS_HAS_JSON_FEATURE; /// Prints the tags for a specific file. #[derive(Parser, Debug, Clone)] pub struct BufferTags { + /// Show the nearest function/method to a specific line. + #[clap(long)] + current_context: Option, + /// Use the raw output format even json output is supported, for testing purpose. #[clap(long)] force_raw: bool, @@ -25,6 +30,13 @@ pub struct BufferTags { impl BufferTags { pub fn run(&self, _params: Params) -> Result<()> { + if let Some(at) = self.current_context { + let context_tag = current_context_tag(self.file.as_path(), at) + .context("Error at finding the context tag info")?; + println!("Context: {:?}", context_tag); + return Ok(()); + } + let lines = if *CTAGS_HAS_JSON_FEATURE.deref() && !self.force_raw { let cmd = build_cmd_in_json_format(self.file.as_ref()); buffer_tags_lines_inner(cmd, BufferTagInfo::from_ctags_json)? @@ -41,19 +53,68 @@ impl BufferTags { } } -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Default)] -struct BufferTagInfo { - name: String, - pattern: String, - line: usize, - kind: String, +const CONTEXT_KINDS: &[&str] = &[ + "function", + "method", + "module", + "macro", + "implementation", + "interface", +]; + +const CONTEXT_SUPERSET: &[&str] = &[ + "function", + "method", + "module", + "macro", + "implementation", + "interface", + "struct", + "field", + "typedef", + "enumerator", +]; + +/// Returns the method/function context associated with line `at`. +pub fn current_context_tag(file: &Path, at: usize) -> Option { + let superset_tags = if *CTAGS_HAS_JSON_FEATURE.deref() { + let cmd = build_cmd_in_json_format(file); + collect_superset_context_tags(cmd, BufferTagInfo::from_ctags_json, at).ok()? + } else { + let cmd = build_cmd_in_raw_format(file); + collect_superset_context_tags(cmd, BufferTagInfo::from_ctags_raw, at).ok()? + }; + + match superset_tags.binary_search_by_key(&at, |tag| tag.line) { + Ok(_l) => None, // Skip if the line is exactly a tag line. + Err(_l) => { + let context_tags = superset_tags + .into_par_iter() + .filter(|tag| CONTEXT_KINDS.contains(&tag.kind.as_ref())) + .collect::>(); + + match context_tags.binary_search_by_key(&at, |tag| tag.line) { + Ok(_) => None, + Err(l) => { + let maybe_idx = l.checked_sub(1); // use the previous item. + maybe_idx.and_then(|idx| context_tags.into_iter().nth(idx)) + } + } + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct BufferTagInfo { + pub name: String, + pub pattern: String, + pub line: usize, + pub kind: String, } impl BufferTagInfo { /// Returns the display line for BuiltinHandle, no icon attached. fn format_buffer_tags(&self, max_name_len: usize) -> String { - let pattern_len = self.pattern.len(); - let name_line = format!("{}:{}", self.name, self.line); let kind = format!("[{}]", self.kind); @@ -63,7 +124,7 @@ impl BufferTagInfo { name_group_width = max_name_len + 6, kind = kind, kind_width = 10, - pattern = self.pattern[2..pattern_len - 2].trim() + pattern = self.extract_pattern().trim() ) } @@ -105,6 +166,11 @@ impl BufferTagInfo { None } } + + pub fn extract_pattern(&self) -> &str { + let pattern_len = self.pattern.len(); + &self.pattern[2..pattern_len - 2] + } } fn build_cmd_in_json_format(file: impl AsRef) -> Exec { @@ -140,14 +206,9 @@ fn buffer_tags_lines_inner( cmd: Exec, parse_fn: impl Fn(&str) -> Option + Send + Sync, ) -> Result> { - use std::io::BufRead; - - let stdout = cmd.stream_stdout()?; - let max_name_len = AtomicUsize::new(0); - let tags = std::io::BufReader::with_capacity(8 * 1024 * 1024, stdout) - .lines() + let tags = crate::utils::lines(cmd)? .flatten() .par_bridge() .filter_map(|s| { @@ -166,3 +227,21 @@ fn buffer_tags_lines_inner( .map(|s| s.format_buffer_tags(max_name_len)) .collect::>()) } + +fn collect_superset_context_tags( + cmd: Exec, + parse_fn: impl Fn(&str) -> Option + Send + Sync, + target_lnum: usize, +) -> Result> { + let mut tags = crate::utils::lines(cmd)? + .flatten() + .par_bridge() + .filter_map(|s| parse_fn(&s)) + // the line of method/function name is lower. + .filter(|tag| tag.line <= target_lnum && CONTEXT_SUPERSET.contains(&tag.kind.as_ref())) + .collect::>(); + + tags.par_sort_unstable_by_key(|x| x.line); + + Ok(tags) +} diff --git a/crates/maple_cli/src/command/grep/mod.rs b/crates/maple_cli/src/command/grep/mod.rs index f72adf835..b25b273f6 100644 --- a/crates/maple_cli/src/command/grep/mod.rs +++ b/crates/maple_cli/src/command/grep/mod.rs @@ -7,9 +7,9 @@ use std::path::{Path, PathBuf}; use std::process::Command; use anyhow::{Context, Result}; +use clap::Parser; use itertools::Itertools; use rayon::prelude::*; -use clap::Parser; use filter::{ matcher::{Bonus, MatchType}, diff --git a/crates/maple_cli/src/dumb_analyzer/find_usages/mod.rs b/crates/maple_cli/src/dumb_analyzer/find_usages/mod.rs index 62a88cb7c..a50e16500 100644 --- a/crates/maple_cli/src/dumb_analyzer/find_usages/mod.rs +++ b/crates/maple_cli/src/dumb_analyzer/find_usages/mod.rs @@ -1,11 +1,33 @@ mod search_engine; +use std::collections::HashMap; use std::ops::{Index, IndexMut}; +use once_cell::sync::OnceCell; use rayon::prelude::*; pub use self::search_engine::{CtagsSearcher, GtagsSearcher, RegexSearcher, SearchType}; +/// Returns a list of comment prefix for a source file. +/// +/// # Argument +/// +/// - `ext`: the extension of a file, e.g., `rs`. +pub fn get_comments_by_ext(ext: &str) -> &[&str] { + static LANGUAGE_COMMENT_TABLE: OnceCell>> = OnceCell::new(); + + let table = LANGUAGE_COMMENT_TABLE.get_or_init(|| { + serde_json::from_str(include_str!( + "../../../../../scripts/dumb_jump/comments_map.json" + )) + .expect("Wrong path for comments_map.json") + }); + + table + .get(ext) + .unwrap_or_else(|| table.get("*").expect("`*` entry exists; qed")) +} + #[derive(Clone, Debug, Default)] pub struct Usage { pub line: String, diff --git a/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/ctags/mod.rs b/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/ctags/mod.rs index 4afa8815d..48b98b563 100644 --- a/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/ctags/mod.rs +++ b/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/ctags/mod.rs @@ -66,17 +66,13 @@ impl<'a, P: AsRef + Hash> CtagsSearcher<'a, P> { search_type: SearchType, force_generate: bool, ) -> Result> { - use std::io::BufRead; - if force_generate || !self.tags_exists() { self.generate_tags()?; } - let stdout = self.build_exec(query, search_type).stream_stdout()?; + let cmd = self.build_exec(query, search_type); - // We usually have a decent amount of RAM nowdays. - Ok(std::io::BufReader::with_capacity(8 * 1024 * 1024, stdout) - .lines() + Ok(crate::utils::lines(cmd)? .flatten() .filter_map(|s| TagInfo::from_readtags(&s))) } diff --git a/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/gtags/mod.rs b/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/gtags/mod.rs index b4341fb4c..5096fc934 100644 --- a/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/gtags/mod.rs +++ b/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/gtags/mod.rs @@ -1,4 +1,3 @@ -use std::io::BufRead; use std::path::{PathBuf, MAIN_SEPARATOR}; use anyhow::{anyhow, Result}; @@ -121,11 +120,7 @@ impl GtagsSearcher { // Returns a stream of tag parsed from the gtags output. fn execute(cmd: Exec) -> Result> { - let stdout = cmd.stream_stdout()?; - - // We usually have a decent amount of RAM nowdays. - Ok(std::io::BufReader::with_capacity(8 * 1024 * 1024, stdout) - .lines() + Ok(crate::utils::lines(cmd)? .flatten() .filter_map(|s| TagInfo::from_gtags(&s))) } diff --git a/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/definition.rs b/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/definition.rs index 5c4893f9a..7abe7165b 100644 --- a/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/definition.rs +++ b/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/definition.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, fmt::Display}; use anyhow::{anyhow, Result}; use itertools::Itertools; -use once_cell::sync::{Lazy, OnceCell}; +use once_cell::sync::Lazy; use rayon::prelude::*; use serde::Deserialize; @@ -106,25 +106,6 @@ pub fn get_language_by_ext(ext: &str) -> Result<&&str> { .ok_or_else(|| anyhow!("dumb_analyzer is unsupported for {}", ext)) } -/// Map of file extension to the comment prefix. -/// -/// Keyed by the extension name. -pub fn get_comments_by_ext(ext: &str) -> &[&str] { - static LANGUAGE_COMMENT_TABLE: OnceCell>> = OnceCell::new(); - - let table = LANGUAGE_COMMENT_TABLE.get_or_init(|| { - let comments: HashMap<&str, Vec<&str>> = serde_json::from_str(include_str!( - "../../../../../../../scripts/dumb_jump/comments_map.json" - )) - .expect("Wrong path for comments_map.json"); - comments - }); - - table - .get(ext) - .unwrap_or_else(|| table.get("*").expect("`*` entry exists; qed")) -} - /// Type of match result of ripgrep. #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Hash)] pub enum MatchKind { diff --git a/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/mod.rs b/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/mod.rs index 11007d645..66ae0917f 100644 --- a/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/mod.rs +++ b/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/mod.rs @@ -19,11 +19,13 @@ use anyhow::Result; use rayon::prelude::*; use self::definition::{ - definitions_and_references, do_search_usages, get_comments_by_ext, get_language_by_ext, - MatchKind, + definitions_and_references, do_search_usages, get_language_by_ext, MatchKind, }; use self::worker::find_occurrences_by_ext; -use crate::dumb_analyzer::find_usages::{Usage, Usages}; +use crate::dumb_analyzer::{ + find_usages::{Usage, Usages}, + get_comments_by_ext, +}; use crate::tools::ripgrep::{Match, Word}; use crate::utils::ExactOrInverseTerms; diff --git a/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/worker.rs b/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/worker.rs index 84cc3af63..af53eb0e5 100644 --- a/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/worker.rs +++ b/crates/maple_cli/src/dumb_analyzer/find_usages/search_engine/regex/worker.rs @@ -4,7 +4,8 @@ use std::path::PathBuf; use anyhow::Result; use rayon::prelude::*; -use super::definition::{build_full_regexp, get_comments_by_ext, is_comment, DefinitionKind}; +use super::definition::{build_full_regexp, is_comment, DefinitionKind}; +use crate::dumb_analyzer::get_comments_by_ext; use crate::process::AsyncCommand; use crate::tools::ripgrep::{Match, Word}; diff --git a/crates/maple_cli/src/dumb_analyzer/mod.rs b/crates/maple_cli/src/dumb_analyzer/mod.rs index dab246ab0..4a8506d14 100644 --- a/crates/maple_cli/src/dumb_analyzer/mod.rs +++ b/crates/maple_cli/src/dumb_analyzer/mod.rs @@ -1,5 +1,5 @@ mod find_usages; pub use self::find_usages::{ - CtagsSearcher, GtagsSearcher, RegexSearcher, SearchType, Usage, Usages, + get_comments_by_ext, CtagsSearcher, GtagsSearcher, RegexSearcher, SearchType, Usage, Usages, }; diff --git a/crates/maple_cli/src/previewer/mod.rs b/crates/maple_cli/src/previewer/mod.rs index f120b324b..21b15cdb9 100644 --- a/crates/maple_cli/src/previewer/mod.rs +++ b/crates/maple_cli/src/previewer/mod.rs @@ -4,6 +4,7 @@ use std::path::Path; use anyhow::{anyhow, Result}; +use types::PreviewInfo; use utility::{read_first_lines, read_preview_lines}; #[inline] @@ -61,10 +62,15 @@ pub fn preview_file_at>( ) -> Result<(Vec, usize)> { tracing::debug!(path = %path.as_ref().display(), lnum, "Previewing file"); - let (lines_iter, hi_lnum) = read_preview_lines(path.as_ref(), lnum, half_size)?; + let PreviewInfo { + lines, + highlight_lnum, + .. + } = read_preview_lines(path.as_ref(), lnum, half_size)?; + let lines = std::iter::once(format!("{}:{}", path.as_ref().display(), lnum)) - .chain(truncate_preview_lines(max_width, lines_iter.into_iter())) + .chain(truncate_preview_lines(max_width, lines.into_iter())) .collect::>(); - Ok((lines, hi_lnum)) + Ok((lines, highlight_lnum)) } diff --git a/crates/maple_cli/src/stdio_server/providers/builtin/mod.rs b/crates/maple_cli/src/stdio_server/providers/builtin/mod.rs index 2d83f320e..f26a2e83b 100644 --- a/crates/maple_cli/src/stdio_server/providers/builtin/mod.rs +++ b/crates/maple_cli/src/stdio_server/providers/builtin/mod.rs @@ -36,7 +36,7 @@ impl EventHandle for BuiltinHandle { async fn on_move(&mut self, msg: MethodCall, context: Arc) -> Result<()> { let msg_id = msg.id; - let source_scale = context.source_scale.lock(); + let source_scale = context.state.source_scale.lock(); let curline = match (source_scale.deref(), msg.get_u64("lnum").ok()) { (SourceScale::Small { ref lines, .. }, Some(lnum)) => { @@ -67,7 +67,7 @@ impl EventHandle for BuiltinHandle { async fn on_typed(&mut self, msg: MethodCall, context: Arc) -> Result<()> { let query = msg.get_query(); - let source_scale = context.source_scale.lock(); + let source_scale = context.state.source_scale.lock(); match source_scale.deref() { SourceScale::Small { ref lines, .. } => { diff --git a/crates/maple_cli/src/stdio_server/providers/builtin/on_move.rs b/crates/maple_cli/src/stdio_server/providers/builtin/on_move.rs index 70e4db360..73f4b52cc 100644 --- a/crates/maple_cli/src/stdio_server/providers/builtin/on_move.rs +++ b/crates/maple_cli/src/stdio_server/providers/builtin/on_move.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -6,7 +7,9 @@ use once_cell::sync::Lazy; use serde_json::json; use pattern::*; +use types::PreviewInfo; +use crate::command::ctags::buffer_tags::{current_context_tag, BufferTagInfo}; use crate::previewer::{self, vim_help::HelpTagPreview}; use crate::stdio_server::{ global, providers::filer, session::SessionContext, write_response, MethodCall, @@ -276,14 +279,69 @@ impl<'a> OnMoveHandler<'a> { let Position { path, lnum } = position; match utility::read_preview_lines(path, *lnum, self.size) { - Ok((lines_iter, hi_lnum)) => { - let fname = path.display().to_string(); - let lines = std::iter::once(format!("{}:{}", fname, lnum)) - .chain(self.truncate_preview_lines(lines_iter.into_iter())) + Ok(PreviewInfo { + lines, + highlight_lnum, + start, + .. + }) => { + let container_width = self.context.display_winwidth as usize; + + // Truncate the left of absolute path string. + let mut fname = path.display().to_string(); + let max_fname_len = container_width - 1 - crate::utils::display_width(*lnum); + if fname.len() > max_fname_len { + if let Some((offset, _)) = + fname.char_indices().nth(fname.len() - max_fname_len + 2) + { + fname.replace_range(..offset, ".."); + } + } + let mut lines = std::iter::once(format!("{}:{}", fname, lnum)) + .chain(self.truncate_preview_lines(lines.into_iter())) .collect::>(); - if let Some(latest_line) = lines.get(hi_lnum) { + let mut highlight_lnum = highlight_lnum; + + // Some checks against the latest preview line. + if let Some(latest_line) = lines.get(highlight_lnum) { self.try_refresh_cache(latest_line); + + if let Some(ext) = path.extension().and_then(|e| e.to_str()) { + let is_comment_line = crate::dumb_analyzer::get_comments_by_ext(ext) + .iter() + .any(|comment| latest_line.trim_start().starts_with(comment)); + + if !is_comment_line { + match current_context_tag(path, *lnum) { + Some(tag) if tag.line < start => { + let border_line = "─".repeat(container_width); + lines.insert(1, border_line.clone()); + + let pattern = tag.extract_pattern(); + // Truncate the right of pattern, 2 whitespaces + 💡 + let max_pattern_len = container_width - 4; + let (mut context_line, to_push) = if pattern.len() + > max_pattern_len + { + // Use the chars instead of indexing the str to avoid the char boundary error. + let p: String = + pattern.chars().take(max_pattern_len - 4 - 2).collect(); + (p, ".. 💡") + } else { + (String::from(pattern), " 💡") + }; + context_line.reserve(to_push.len()); + context_line.push_str(to_push); + lines.insert(1, context_line); + + lines.insert(1, border_line); + highlight_lnum += 3; + } + _ => {} + } + } + } } tracing::debug!( @@ -297,7 +355,7 @@ impl<'a> OnMoveHandler<'a> { "event": "on_move", "lines": lines, "fname": fname, - "hi_lnum": hi_lnum + "hi_lnum": highlight_lnum })); } Err(err) => { diff --git a/crates/maple_cli/src/stdio_server/providers/custom/dumb_jump/searcher.rs b/crates/maple_cli/src/stdio_server/providers/custom/dumb_jump/searcher.rs index d90b1972e..34823d7f7 100644 --- a/crates/maple_cli/src/stdio_server/providers/custom/dumb_jump/searcher.rs +++ b/crates/maple_cli/src/stdio_server/providers/custom/dumb_jump/searcher.rs @@ -1,6 +1,8 @@ use std::path::Path; use anyhow::Result; +use itertools::Itertools; +use rayon::prelude::*; use super::SearchInfo; use crate::dumb_analyzer::{ @@ -25,6 +27,8 @@ fn search_ctags(dir: &Path, extension: &str, search_info: &SearchInfo) -> Result let usages = CtagsSearcher::new(tags_config) .search(keyword, search_type.clone(), true)? + .sorted_by_key(|t| t.line) // Ensure the tags are sorted as the definition goes first and then the implementations. + .par_bridge() .filter_map(|tag_line| { let (line, indices) = tag_line.grep_format_ctags(keyword, ignorecase); filtering_terms @@ -44,6 +48,7 @@ fn search_gtags(dir: &Path, search_info: &SearchInfo) -> Result { } = search_info; let usages = GtagsSearcher::new(dir.to_path_buf()) .search_references(keyword)? + .par_bridge() .filter_map(|tag_info| { let (line, indices) = tag_info.grep_format_gtags("refs", keyword, false); filtering_terms diff --git a/crates/maple_cli/src/stdio_server/session/context.rs b/crates/maple_cli/src/stdio_server/session/context.rs index f3146ecf2..9a13f0dba 100644 --- a/crates/maple_cli/src/stdio_server/session/context.rs +++ b/crates/maple_cli/src/stdio_server/session/context.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::path::PathBuf; use std::sync::{atomic::AtomicBool, Arc}; @@ -9,6 +10,7 @@ use matcher::MatchType; use parking_lot::Mutex; use serde::Deserialize; +use crate::command::ctags::buffer_tags::BufferTagInfo; use crate::stdio_server::{ rpc::{Call, MethodCall, Notification}, types::ProviderId, @@ -70,6 +72,20 @@ impl SourceScale { } } +// TODO: cache the buffer tags per session. +#[derive(Debug, Clone)] +pub struct CachedBufTags { + pub done: bool, + pub tags: Vec, +} + +#[derive(Debug, Clone)] +pub struct SessionState { + pub is_running: Arc, + pub source_scale: Arc>, + pub buf_tags_cache: Arc>>, +} + #[derive(Debug, Clone)] pub struct SessionContext { pub provider_id: ProviderId, @@ -82,10 +98,9 @@ pub struct SessionContext { pub icon: Icon, pub match_type: MatchType, pub match_bonuses: Vec, - pub source_scale: Arc>, pub source_cmd: Option, pub runtimepath: Option, - pub is_running: Arc>, + pub state: SessionState, } impl SessionContext { @@ -112,7 +127,7 @@ impl SessionContext { } pub fn set_source_scale(&self, new: SourceScale) { - let mut source_scale = self.source_scale.lock(); + let mut source_scale = self.state.source_scale.lock(); *source_scale = new; } @@ -182,8 +197,11 @@ impl SessionContext { match_type, match_bonuses, icon, - source_scale: Arc::new(Mutex::new(SourceScale::Indefinite)), - is_running: Arc::new(Mutex::new(true.into())), + state: SessionState { + is_running: Arc::new(true.into()), + source_scale: Arc::new(Mutex::new(SourceScale::Indefinite)), + buf_tags_cache: Arc::new(Mutex::new(HashMap::new())), + }, } } } diff --git a/crates/maple_cli/src/stdio_server/session/mod.rs b/crates/maple_cli/src/stdio_server/session/mod.rs index caff20e36..cd95ef216 100644 --- a/crates/maple_cli/src/stdio_server/session/mod.rs +++ b/crates/maple_cli/src/stdio_server/session/mod.rs @@ -3,7 +3,7 @@ mod manager; use std::borrow::Cow; use std::collections::HashSet; -use std::sync::Arc; +use std::sync::{atomic::Ordering, Arc}; use std::time::Duration; use anyhow::Result; @@ -145,8 +145,7 @@ impl Session { /// Sets the running signal to false, in case of the forerunner thread is still working. pub fn handle_terminate(&mut self) { - let mut val = self.context.is_running.lock(); - *val.get_mut() = false; + self.context.state.is_running.store(false, Ordering::SeqCst); tracing::debug!( session_id = self.session_id, provider_id = %self.provider_id(), diff --git a/crates/maple_cli/src/tools/ripgrep/mod.rs b/crates/maple_cli/src/tools/ripgrep/mod.rs index 6356c19bb..ebd29723b 100644 --- a/crates/maple_cli/src/tools/ripgrep/mod.rs +++ b/crates/maple_cli/src/tools/ripgrep/mod.rs @@ -7,6 +7,8 @@ use std::{borrow::Cow, convert::TryFrom}; use anyhow::Result; +use crate::utils::display_width; + pub use self::jsont::{Match, Message, SubMatch}; /// Word represents the input query around by word boundries. @@ -130,23 +132,6 @@ impl TryFrom<&str> for Match { } } -/// Returns the width of displaying `n` on the screen. -/// -/// Same with `n.to_string().len()` but without allocation. -fn display_width(mut n: usize) -> usize { - if n == 0 { - return 1; - } - - let mut len = 0; - while n > 0 { - len += 1; - n /= 10; - } - - len -} - impl Match { /// Returns a pair of the formatted `String` and the offset of origin match indices. /// diff --git a/crates/maple_cli/src/utils.rs b/crates/maple_cli/src/utils.rs index d004b5401..2fd064158 100644 --- a/crates/maple_cli/src/utils.rs +++ b/crates/maple_cli/src/utils.rs @@ -1,10 +1,14 @@ -use std::path::{Path, PathBuf}; +use std::{ + io::{BufRead, Lines}, + path::{Path, PathBuf}, +}; use anyhow::{anyhow, Result}; use chrono::prelude::*; use directories::ProjectDirs; use once_cell::sync::Lazy; +use filter::subprocess::Exec; use icon::Icon; use types::{ExactTerm, InverseTerm}; use utility::{println_json, println_json_with_length, read_first_lines}; @@ -18,21 +22,12 @@ pub static PROJECT_DIRS: Lazy = Lazy::new(|| { }); /// Yes or no terms. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ExactOrInverseTerms { pub exact_terms: Vec, pub inverse_terms: Vec, } -impl Default for ExactOrInverseTerms { - fn default() -> Self { - Self { - exact_terms: Vec::new(), - inverse_terms: Vec::new(), - } - } -} - impl ExactOrInverseTerms { /// Returns the match indices of exact terms if given `line` passes all the checks. fn check_terms(&self, line: &str) -> Option> { @@ -202,8 +197,6 @@ pub fn build_abs_path(cwd: impl AsRef, curline: impl AsRef) -> PathB /// /// Credit: https://github.com/eclarke/linecount/blob/master/src/lib.rs pub fn count_lines(handle: R) -> Result { - use std::io::BufRead; - let mut reader = std::io::BufReader::with_capacity(1024 * 32, handle); let mut count = 0; loop { @@ -221,6 +214,30 @@ pub fn count_lines(handle: R) -> Result Ok(count) } +#[inline] +pub fn lines(cmd: Exec) -> Result> { + // We usually have a decent amount of RAM nowdays. + Ok(std::io::BufReader::with_capacity(8 * 1024 * 1024, cmd.stream_stdout()?).lines()) +} + +/// Returns the width of displaying `n` on the screen. +/// +/// Same with `n.to_string().len()` but without allocation. +pub fn display_width(n: usize) -> usize { + if n == 0 { + return 1; + } + + let mut n = n; + let mut len = 0; + while n > 0 { + len += 1; + n /= 10; + } + + len +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/printer/src/truncation.rs b/crates/printer/src/truncation.rs index 1bcf6e7b6..e5059be91 100644 --- a/crates/printer/src/truncation.rs +++ b/crates/printer/src/truncation.rs @@ -54,6 +54,8 @@ fn truncate_line_v1( } } +const MAX_LINE_LEN: usize = 500; + /// Long matched lines can cause the matched items invisible. /// /// # Arguments @@ -68,14 +70,25 @@ pub fn truncate_long_matched_lines( let mut truncated_map = HashMap::new(); let winwidth = winwidth - WINWIDTH_OFFSET; items.enumerate().for_each(|(lnum, mut filtered_item)| { - let source_item = &filtered_item.source_item; - if let Some((truncated, truncated_indices)) = truncate_line_v1( - source_item.display_text(), + let origin_display_text = filtered_item.source_item.display_text(); + + // Truncate the text simply if it's too long. + if origin_display_text.len() > MAX_LINE_LEN { + let display_text: String = origin_display_text.chars().take(1000).collect(); + filtered_item.display_text = Some(display_text); + filtered_item.match_indices = filtered_item + .match_indices + .iter() + .filter(|x| **x < 1000) + .copied() + .collect(); + } else if let Some((truncated, truncated_indices)) = truncate_line_v1( + origin_display_text, &mut filtered_item.match_indices, winwidth, skipped, ) { - truncated_map.insert(lnum + 1, source_item.display_text().to_string()); + truncated_map.insert(lnum + 1, origin_display_text.to_string()); filtered_item.display_text = Some(truncated); filtered_item.match_indices = truncated_indices; diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index fd8dbc2fc..12598582b 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -8,3 +8,14 @@ pub use self::search_term::{ TermType, }; pub use self::source_item::{FilteredItem, FuzzyText, MatchType, MatchingText, SourceItem}; + +/// The preview content is usually part of a file. +#[derive(Clone, Debug)] +pub struct PreviewInfo { + pub start: usize, + pub end: usize, + /// Line number of the line that should be highlighed in the preview window. + pub highlight_lnum: usize, + /// [start, end] of the source file. + pub lines: Vec, +} diff --git a/crates/utility/Cargo.toml b/crates/utility/Cargo.toml index e7e228568..a07ee96f7 100644 --- a/crates/utility/Cargo.toml +++ b/crates/utility/Cargo.toml @@ -9,3 +9,5 @@ anyhow = "1.0" directories = "3.0" memchr = "2.4" simdutf8 = "0.1" + +types = { path = "../types" } diff --git a/crates/utility/src/lib.rs b/crates/utility/src/lib.rs index e6619aa22..38a8f4ad0 100644 --- a/crates/utility/src/lib.rs +++ b/crates/utility/src/lib.rs @@ -9,6 +9,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use anyhow::{anyhow, Result}; +use types::PreviewInfo; use self::bytelines::ByteLines; @@ -86,7 +87,7 @@ fn read_preview_lines_utf8>( size: usize, ) -> io::Result<(impl Iterator, usize)> { let file = File::open(path)?; - let (start, end, hl_line) = if target_line > size { + let (start, end, highlight_lnum) = if target_line > size { (target_line - size, target_line + size, size) } else { (0, 2 * size, target_line) @@ -97,7 +98,7 @@ fn read_preview_lines_utf8>( .skip(start) .filter_map(|l| l.ok()) .take(end - start), - hl_line, + highlight_lnum, )) } @@ -106,7 +107,7 @@ pub fn read_preview_lines>( path: P, target_line: usize, size: usize, -) -> io::Result<(Vec, usize)> { +) -> io::Result { read_preview_lines_impl(path, target_line, size) } @@ -123,8 +124,8 @@ fn read_preview_lines_impl>( path: P, target_line: usize, size: usize, -) -> io::Result<(Vec, usize)> { - let (start, end, hl_line) = if target_line > size { +) -> io::Result { + let (start, end, highlight_lnum) = if target_line > size { (target_line - size, target_line + size, size) } else { (0, 2 * size, target_line) @@ -149,15 +150,19 @@ fn read_preview_lines_impl>( file.read_to_end(&mut filebuf) }) .map(|_| { - ( - ByteLines::new(&filebuf) - .into_iter() - .skip(start) - .take(end - start) - .map(|l| l.to_string()) - .collect::>(), - hl_line, - ) + let lines = ByteLines::new(&filebuf) + .into_iter() + .skip(start) + .take(end - start) + .map(|l| l.to_string()) + .collect::>(); + + PreviewInfo { + start, + end, + highlight_lnum, + lines, + } }) } @@ -230,7 +235,7 @@ mod tests { fn test_multi_byte_reading() { let mut current_dir = std::env::current_dir().unwrap(); current_dir.push("test_673.txt"); - let (lines, _hl_line) = read_preview_lines_impl(current_dir, 2, 5).unwrap(); + let PreviewInfo { lines, .. } = read_preview_lines_impl(current_dir, 2, 5).unwrap(); assert_eq!( lines, [ diff --git a/pythonx/clap/fuzzymatch-rs/Cargo.lock b/pythonx/clap/fuzzymatch-rs/Cargo.lock index e1f7ce739..c8e94ff5a 100644 --- a/pythonx/clap/fuzzymatch-rs/Cargo.lock +++ b/pythonx/clap/fuzzymatch-rs/Cargo.lock @@ -624,6 +624,7 @@ dependencies = [ "directories", "memchr", "simdutf8", + "types", ] [[package]] diff --git a/src/main.rs b/src/main.rs index 8ef6b6c47..ddcc76ea8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use maple_cli::{Cmd, Context, Maple, Result, Parser}; +use maple_cli::{Cmd, Context, Maple, Parser, Result}; pub mod built_info { include!(concat!(env!("OUT_DIR"), "/built.rs"));