diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 31d8837..bb72765 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,5 +24,10 @@ jobs: if: matrix.os == 'ubuntu-latest' - run: cargo clippy --all-targets --tests -- -D warnings - run: cargo test + if: matrix.os == 'ubuntu-latest' + env: + TERM: linux + - run: cargo test + if: matrix.os != 'ubuntu-latest' - run: RUSTDOCFLAGS='--deny warnings' cargo doc --no-deps if: matrix.os == 'ubuntu-latest' diff --git a/Cargo.toml b/Cargo.toml index 56dbf82..5d6bba4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ bytecount = "0.6" clap = { version = "4.3.0", features = ["derive", "wrap_help"] } encoding_rs = "0.8.14" encoding_rs_io = "0.1.6" +grep-cli = "0.1.8" ignore = { package = "tree_sitter_grep_ignore", git = "https://github.com/helixbass/ripgrep", rev = "669ebd3", version = "0.4.20-dev.0" } libc = "0.2.144" libloading = "0.8.0" @@ -73,6 +74,7 @@ assert_cmd = "2.0.11" escargot = "0.5.7" predicates = "3.0.3" shlex = "1.1.0" +text-diff = "0.4.0" [features] default = ["bytecount/runtime-dispatch-simd"] diff --git a/src/args.rs b/src/args.rs index 9b2ec48..205cf5a 100644 --- a/src/args.rs +++ b/src/args.rs @@ -3,14 +3,14 @@ use std::{ sync::{Arc, Mutex}, }; -use clap::{ArgGroup, Parser}; +use clap::{ArgGroup, Parser, ValueEnum}; use ignore::{types::Types, WalkBuilder, WalkParallel}; use rayon::iter::IterBridge; -use termcolor::BufferWriter; +use termcolor::{BufferWriter, ColorChoice}; use crate::{ language::SupportedLanguage, - printer::StandardBuilder, + printer::{default_color_specs, ColorSpecs, StandardBuilder, UserColorSpec}, project_file_walker::{ get_project_file_walker_types, into_parallel_iterator, WalkParallelIterator, }, @@ -19,6 +19,18 @@ use crate::{ NonFatalError, }; +#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] +pub enum ColorChoiceArg { + /// Colors will never be used. + Never, + /// The default. tree-sitter-grep tries to be smart. + Auto, + /// Colors will always be used regardless of where output is sent. + Always, + /// Like 'always', but emits ANSI escapes (even in a Windows console). + Ansi, +} + #[derive(Parser)] #[clap(group( ArgGroup::new("query_or_filter") @@ -107,10 +119,137 @@ pub struct Args { /// the offset of the matching part itself. #[arg(short = 'b', long)] pub byte_offset: bool, + + /// This flag specifies color settings for use in the output. + /// + /// This flag may be provided multiple times. Settings are applied + /// iteratively. Colors are limited to one of eight choices: red, blue, + /// green, cyan, magenta, yellow, white and black. Styles are limited to + /// nobold, bold, nointense, intense, nounderline or underline. + /// + /// The format of the flag is '{type}:{attribute}:{value}'. '{type}' should + /// be one of path, line, column or match. '{attribute}' can be fg, bg + /// or style. '{value}' is either a color (for fg and bg) or a text + /// style. A special format, '{type}:none', will clear all color + /// settings for '{type}'. + /// + /// For example, the following command will change the match color to + /// magenta and the background color for line numbers to yellow: + /// + /// tree-sitter-grep --colors 'match:fg:magenta' --colors 'line:bg:yellow' + /// -q '(function_item) @f' + /// + /// Extended colors can be used for '{value}' when the terminal supports + /// ANSI color sequences. These are specified as either 'x' (256-color) + /// or 'x,x,x' (24-bit truecolor) where x is a number between 0 and 255 + /// inclusive. x may be given as a normal decimal number or a + /// hexadecimal number, which is prefixed by `0x`. + /// + /// For example, the following command will change the match background + /// color to that represented by the rgb value (0,128,255): + /// + /// tree-sitter-grep --colors 'match:bg:0,128,255' + /// + /// or, equivalently, + /// + /// tree-sitter-grep --colors 'match:bg:0x0,0x80,0xFF' + /// + /// Note that the intense and nointense style flags will have no effect when + /// used alongside these extended color codes. + #[arg(long)] + pub colors: Vec, + + /// This flag controls when to use colors. + /// + /// The default setting is 'auto', which means tree-sitter-grep will try to + /// guess when to use colors. For example, if tree-sitter-grep is printing + /// to a terminal, then it will use colors, but if it is redirected to a + /// file or a pipe, then it will suppress color output. tree-sitter-grep + /// will suppress color output in some other circumstances as well. For + /// example, if the TERM environment variable is not set or set to 'dumb', + /// then tree-sitter-grep will not use colors. + /// + /// When the --vimgrep flag is given to tree-sitter-grep, then the default + /// value for the --color flag changes to 'never'. + #[arg(long, value_name = "WHEN")] + pub color: Option, + + /// This is a convenience alias for '--color always --heading + /// --line-number'. + /// + /// This flag is useful when you still want pretty output even if you're + /// piping tree-sitter-grep to another program or file. For example: + /// 'tree-sitter-grep -p -q "(function_item) @c" | less -R'. + #[arg(short = 'p', long)] + pub pretty: bool, + + /// This flag prints the file path above clusters of matches from each file + /// instead of printing the file path as a prefix for each matched line. + /// + /// This is the default mode when printing to a terminal. + /// + /// This overrides the --no-heading flag. + #[arg(long)] + pub heading: bool, + + /// Don't group matches by each file. + /// + /// If --no-heading is provided in addition to the -H/--with-filename flag, + /// then file paths will be printed as a prefix for every matched line. + /// This is the default mode when not printing to a terminal. + /// + /// This overrides the --heading flag. + #[arg(long, overrides_with = "heading")] + pub no_heading: bool, + + /// Display the file path for matches. + /// + /// This is the default when more than one file is searched. If --heading is + /// enabled (the default when printing to a terminal), the file path will be + /// shown above clusters of matches from each file; otherwise, the file name + /// will be shown as a prefix for each matched line. + /// + /// This flag overrides --no-filename. + #[arg(short = 'H', long)] + pub with_filename: bool, + + /// Never print the file path with the matched lines. + /// + /// This is the default when tree-sitter-grep is explicitly instructed to + /// search one file or stdin. + /// + /// This flag overrides --with-filename. + #[arg(short = 'I', long, overrides_with = "with_filename")] + pub no_filename: bool, + + /// Show line numbers (1-based). + /// + /// This is enabled by default when searching in a terminal. + #[arg(short = 'n', long)] + pub line_number: bool, + + /// Suppress line numbers. + /// + /// This is enabled by default when not searching in a terminal. + #[arg(short = 'N', long, overrides_with = "line_number")] + pub no_line_number: bool, + + /// Show column numbers (1-based). + /// + /// This only shows the column numbers for the first match on each line. + /// This does not try to account for Unicode. One byte is equal to one + /// column. This implies --line-number. + /// + /// This flag can be disabled with --no-column. + #[arg(long)] + pub column: bool, + + #[arg(long, hide = true, overrides_with = "column")] + pub no_column: bool, } impl Args { - fn use_paths(&self) -> Vec { + pub fn use_paths(&self) -> Vec { if self.paths.is_empty() { vec![Path::new("./").to_owned()] } else { @@ -122,8 +261,26 @@ impl Args { self.paths.is_empty() } - fn line_number(&self) -> bool { - true + fn is_only_stdin(&self, paths: &[PathBuf]) -> bool { + paths == [Path::new("-")] + } + + fn line_number(&self, paths: &[PathBuf]) -> bool { + // if self.output_kind() == OutputKind::Summary { + // return false; + // } + if self.no_line_number { + return false; + } + // if self.output_kind() == OutputKind::JSON { + // return true; + // } + + (grep_cli::is_tty_stdout() && !self.is_only_stdin(paths)) + || self.line_number + || self.column + || self.pretty + || self.vimgrep } fn per_match(&self) -> bool { @@ -135,7 +292,10 @@ impl Args { } fn column(&self) -> bool { - self.vimgrep + if self.no_column { + return false; + } + self.column || self.vimgrep } fn contexts(&self) -> (usize, usize) { @@ -150,22 +310,27 @@ impl Args { } } - pub(crate) fn get_searcher(&self) -> Searcher { + pub(crate) fn get_searcher(&self, paths: &[PathBuf]) -> Searcher { let (before_context, after_context) = self.contexts(); SearcherBuilder::new() - .line_number(self.line_number()) + .line_number(self.line_number(paths)) .before_context(before_context) .after_context(after_context) .build() } - pub(crate) fn get_printer(&self, buffer_writer: &BufferWriter) -> Printer { + pub(crate) fn get_printer(&self, paths: &[PathBuf], buffer_writer: &BufferWriter) -> Printer { StandardBuilder::new() + .color_specs(self.color_specs()) + .heading(self.heading()) + .path(self.with_filename(paths)) .per_match(self.per_match()) .per_match_one_line(self.per_match_one_line()) .column(self.column()) .only_matching(self.only_matching) .byte_offset(self.byte_offset) + .separator_context(self.context_separator()) + .separator_search(None) .build(buffer_writer.buffer()) } @@ -190,4 +355,78 @@ impl Args { ) -> IterBridge { into_parallel_iterator(self.get_project_file_walker(), non_fatal_errors) } + + pub fn color_specs(&self) -> ColorSpecs { + let mut specs = default_color_specs(); + for user_color_spec in &self.colors { + specs.push(user_color_spec.clone()); + } + ColorSpecs::new(&specs) + } + + pub fn buffer_writer(&self) -> BufferWriter { + let mut wtr = BufferWriter::stdout(self.color_choice()); + wtr.separator(self.file_separator()); + wtr + } + + fn color_choice(&self) -> ColorChoice { + match self.color.unwrap_or(if self.vimgrep { + ColorChoiceArg::Never + } else { + ColorChoiceArg::Auto + }) { + ColorChoiceArg::Always => ColorChoice::Always, + ColorChoiceArg::Ansi => ColorChoice::AlwaysAnsi, + ColorChoiceArg::Auto => { + if grep_cli::is_tty_stdout() || self.pretty { + ColorChoice::Auto + } else { + ColorChoice::Never + } + } + ColorChoiceArg::Never => ColorChoice::Never, + } + } + + fn heading(&self) -> bool { + if self.no_heading || self.vimgrep { + false + } else { + grep_cli::is_tty_stdout() || self.heading || self.pretty + } + } + + fn file_separator(&self) -> Option> { + // if self.output_kind() != OutputKind::Standard { + // return Ok(None); + // } + + let (ctx_before, ctx_after) = self.contexts(); + if self.heading() { + Some(b"".to_vec()) + } else if ctx_before > 0 || ctx_after > 0 { + self.context_separator() + } else { + None + } + } + + fn context_separator(&self) -> Option> { + Some(b"--".to_vec()) + } + + fn with_filename(&self, paths: &[PathBuf]) -> bool { + if self.no_filename { + false + } else { + let path_stdin = Path::new("-"); + self.with_filename + || self.vimgrep + || paths.len() > 1 + || paths + .get(0) + .map_or(false, |p| p != path_stdin && p.is_dir()) + } + } } diff --git a/src/lib.rs b/src/lib.rs index 358fc5c..f1ad1d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,6 @@ use std::{ use ignore::DirEntry; use plugin::get_loaded_filter; use rayon::prelude::*; -use termcolor::{BufferWriter, ColorChoice}; use thiserror::Error; use tree_sitter::{Query, QueryError}; @@ -273,7 +272,7 @@ pub fn run(args: Args) -> Result { get_loaded_filter(args.filter.as_deref(), args.filter_arg.as_deref())?.map(Arc::new); let cached_queries: CachedQueries = Default::default(); let capture_index = CaptureIndex::default(); - let buffer_writer = BufferWriter::stdout(ColorChoice::Never); + let buffer_writer = args.buffer_writer(); let matched = AtomicBool::new(false); let searched = AtomicBool::new(false); let non_fatal_errors: Arc>> = Default::default(); diff --git a/src/printer/color.rs b/src/printer/color.rs index abd8823..adca1f1 100644 --- a/src/printer/color.rs +++ b/src/printer/color.rs @@ -4,16 +4,15 @@ use std::{error, fmt, str::FromStr}; use termcolor::{Color, ColorSpec, ParseColorError}; -#[allow(dead_code)] pub fn default_color_specs() -> Vec { vec![ - #[cfg(unix)] - "path:fg:magenta".parse().unwrap(), - #[cfg(windows)] - "path:fg:cyan".parse().unwrap(), - "line:fg:green".parse().unwrap(), - "match:fg:red".parse().unwrap(), - "match:style:bold".parse().unwrap(), + "path:fg:green".parse().unwrap(), + "path:style:bold".parse().unwrap(), + "line:fg:yellow".parse().unwrap(), + "line:style:bold".parse().unwrap(), + "match:fg:black".parse().unwrap(), + "match:bg:yellow".parse().unwrap(), + "match:style:nobold".parse().unwrap(), ] } diff --git a/src/printer/mod.rs b/src/printer/mod.rs index dac72de..2dd154f 100644 --- a/src/printer/mod.rs +++ b/src/printer/mod.rs @@ -4,4 +4,5 @@ mod standard; mod stats; mod util; +pub use color::{default_color_specs, ColorError, ColorSpecs, UserColorSpec}; pub use standard::{Standard, StandardBuilder}; diff --git a/src/use_printer.rs b/src/use_printer.rs index 1db917d..476bf9a 100644 --- a/src/use_printer.rs +++ b/src/use_printer.rs @@ -15,8 +15,14 @@ thread_local! { } pub(crate) fn get_printer(buffer_writer: &BufferWriter, args: &Args) -> Rc> { PRINTER.with(|printer| { - let (printer, args_when_initialized) = - printer.get_or_init(|| (Rc::new(RefCell::new(args.get_printer(buffer_writer))), args)); + let (printer, args_when_initialized) = printer.get_or_init(|| { + ( + Rc::new(RefCell::new( + args.get_printer(&args.use_paths(), buffer_writer), + )), + args, + ) + }); assert!( ptr::eq(*args_when_initialized, args), "Using multiple instances of args not supported" diff --git a/src/use_searcher.rs b/src/use_searcher.rs index 10d824a..ebe021d 100644 --- a/src/use_searcher.rs +++ b/src/use_searcher.rs @@ -11,8 +11,12 @@ thread_local! { } pub(crate) fn get_searcher(args: &Args) -> Rc> { SEARCHER.with(|searcher| { - let (searcher, args_when_initialized) = - searcher.get_or_init(|| (Rc::new(RefCell::new(args.get_searcher())), args)); + let (searcher, args_when_initialized) = searcher.get_or_init(|| { + ( + Rc::new(RefCell::new(args.get_searcher(&args.use_paths()))), + args, + ) + }); assert!( ptr::eq(*args_when_initialized, args), "Using multiple instances of args not supported" diff --git a/tests/languages.rs b/tests/languages.rs index 11e305d..a9fa15b 100644 --- a/tests/languages.rs +++ b/tests/languages.rs @@ -8,7 +8,7 @@ fn test_swift() { "swift_project", r#" $ tree-sitter-grep -q '(value_argument) @c' --language swift - example.swift:2: atPath: "native" + example.swift: atPath: "native" "#, ); } @@ -19,7 +19,7 @@ fn test_swift_auto_language() { "swift_project", r#" $ tree-sitter-grep -q '(value_argument) @c' - example.swift:2: atPath: "native" + example.swift: atPath: "native" "#, ); } @@ -30,7 +30,7 @@ fn test_objective_c() { "objective_c_project", r#" $ tree-sitter-grep -q '(struct_declaration) @c' --language objective-c - example.h:4:@property (nonatomic, strong, nullable) NSString *baseURL; + example.h:@property (nonatomic, strong, nullable) NSString *baseURL; "#, ); } @@ -41,7 +41,7 @@ fn test_objective_c_auto_language() { "objective_c_project", r#" $ tree-sitter-grep -q '(struct_declaration) @c' - example.h:4:@property (nonatomic, strong, nullable) NSString *baseURL; + example.h:@property (nonatomic, strong, nullable) NSString *baseURL; "#, ); } @@ -63,9 +63,9 @@ fn test_toml() { "rust_project", r#" $ tree-sitter-grep -q '(string) @c' --language toml - Cargo.toml:2:name = "rust_project" - Cargo.toml:3:version = "0.1.0" - Cargo.toml:4:edition = "2021" + Cargo.toml:name = "rust_project" + Cargo.toml:version = "0.1.0" + Cargo.toml:edition = "2021" "#, ); } @@ -76,9 +76,9 @@ fn test_toml_auto_language() { "rust_project", r#" $ tree-sitter-grep -q '(string) @c' - Cargo.toml:2:name = "rust_project" - Cargo.toml:3:version = "0.1.0" - Cargo.toml:4:edition = "2021" + Cargo.toml:name = "rust_project" + Cargo.toml:version = "0.1.0" + Cargo.toml:edition = "2021" "#, ); } @@ -89,8 +89,8 @@ fn test_python() { "python_project", r#" $ tree-sitter-grep -q '(for_statement) @c' --language python - example.py:2: for x in y: - example.py:3: something() + example.py: for x in y: + example.py: something() "#, ); } @@ -101,8 +101,8 @@ fn test_python_auto_language() { "python_project", r#" $ tree-sitter-grep -q '(for_statement) @c' - example.py:2: for x in y: - example.py:3: something() + example.py: for x in y: + example.py: something() "#, ); } @@ -113,7 +113,7 @@ fn test_ruby() { "ruby_project", r#" $ tree-sitter-grep -q '(binary) @c' --language ruby - example.rb:1:if x > y + example.rb:if x > y "#, ); } @@ -124,7 +124,7 @@ fn test_ruby_auto_language() { "ruby_project", r#" $ tree-sitter-grep -q '(binary) @c' - example.rb:1:if x > y + example.rb:if x > y "#, ); } @@ -135,7 +135,7 @@ fn test_c() { "c_project", r#" $ tree-sitter-grep -q '(pointer_declarator) @c' --language c - example.h:1:void r_bin_object_free(void /*RBinObject*/ *o_); + example.h:void r_bin_object_free(void /*RBinObject*/ *o_); "#, ); } @@ -157,7 +157,7 @@ fn test_cpp() { "cpp_project", r#" $ tree-sitter-grep -q '(namespace_identifier) @c' --language c++ - example.cpp:1:const AvailableAttr *DeclAttributes::getUnavailable( + example.cpp:const AvailableAttr *DeclAttributes::getUnavailable( "#, ); } @@ -168,7 +168,7 @@ fn test_cpp_auto_language() { "cpp_project", r#" $ tree-sitter-grep -q '(namespace_identifier) @c' - example.cpp:1:const AvailableAttr *DeclAttributes::getUnavailable( + example.cpp:const AvailableAttr *DeclAttributes::getUnavailable( "#, ); } @@ -179,7 +179,7 @@ fn test_go() { "go_project", r#" $ tree-sitter-grep -q '(import_spec) @c' --language go - example.go:2: "context" + example.go: "context" "#, ); } @@ -190,7 +190,7 @@ fn test_go_auto_language() { "go_project", r#" $ tree-sitter-grep -q '(import_spec) @c' - example.go:2: "context" + example.go: "context" "#, ); } @@ -201,7 +201,7 @@ fn test_java() { "java_project", r#" $ tree-sitter-grep -q '(marker_annotation) @c' --language java - example.java:1:@ThreadSafe + example.java:@ThreadSafe "#, ); } @@ -212,7 +212,7 @@ fn test_java_auto_language() { "java_project", r#" $ tree-sitter-grep -q '(marker_annotation) @c' - example.java:1:@ThreadSafe + example.java:@ThreadSafe "#, ); } @@ -223,7 +223,7 @@ fn test_c_sharp() { "csharp_project", r#" $ tree-sitter-grep -q '(qualified_name) @c' --language c-sharp - example.cs:1:namespace YL.Utils.Json {} + example.cs:namespace YL.Utils.Json {} "#, ); } @@ -234,7 +234,7 @@ fn test_c_sharp_auto_language() { "csharp_project", r#" $ tree-sitter-grep -q '(qualified_name) @c' - example.cs:1:namespace YL.Utils.Json {} + example.cs:namespace YL.Utils.Json {} "#, ); } @@ -245,7 +245,7 @@ fn test_kotlin() { "kotlin_project", r#" $ tree-sitter-grep -q '(user_type) @c' --language kotlin - example.kt:2: val barA: Int + example.kt: val barA: Int "#, ); } @@ -256,7 +256,7 @@ fn test_kotlin_auto_language() { "kotlin_project", r#" $ tree-sitter-grep -q '(user_type) @c' - example.kt:2: val barA: Int + example.kt: val barA: Int "#, ); } @@ -267,8 +267,8 @@ fn test_elisp() { "elisp_project", r#" $ tree-sitter-grep -q '(quote) @c' --language elisp - example.el:3: :group 'lsp-sourcekit - example.el:4: :type 'file) + example.el: :group 'lsp-sourcekit + example.el: :type 'file) "#, ); } @@ -279,8 +279,8 @@ fn test_elisp_auto_language() { "elisp_project", r#" $ tree-sitter-grep -q '(quote) @c' - example.el:3: :group 'lsp-sourcekit - example.el:4: :type 'file) + example.el: :group 'lsp-sourcekit + example.el: :type 'file) "#, ); } @@ -291,7 +291,7 @@ fn test_elm() { "elm_project", r#" $ tree-sitter-grep -q '(upper_case_qid) @c' --language elm - example.elm:1:import Lofi.Schema exposing (Schema, Item, Kind(..)) + example.elm:import Lofi.Schema exposing (Schema, Item, Kind(..)) "#, ); } @@ -302,7 +302,7 @@ fn test_elm_auto_language() { "elm_project", r#" $ tree-sitter-grep -q '(upper_case_qid) @c' - example.elm:1:import Lofi.Schema exposing (Schema, Item, Kind(..)) + example.elm:import Lofi.Schema exposing (Schema, Item, Kind(..)) "#, ); } @@ -313,7 +313,7 @@ fn test_dockerfile() { "dockerfile_project", r#" $ tree-sitter-grep -q '(path) @c' --language dockerfile - Dockerfile:1:WORKDIR /usr/src/app + Dockerfile:WORKDIR /usr/src/app "#, ); } @@ -324,7 +324,7 @@ fn test_dockerfile_auto_language() { "dockerfile_project", r#" $ tree-sitter-grep -q '(path) @c' - Dockerfile:1:WORKDIR /usr/src/app + Dockerfile:WORKDIR /usr/src/app "#, ); } @@ -335,7 +335,7 @@ fn test_html() { "html_project", r#" $ tree-sitter-grep -q '(text) @c' --language html - example.html:3:

hello

+ example.html:

hello

"#, ); } @@ -346,7 +346,7 @@ fn test_html_auto_language() { "html_project", r#" $ tree-sitter-grep -q '(text) @c' - example.html:3:

hello

+ example.html:

hello

"#, ); } @@ -357,7 +357,7 @@ fn test_tree_sitter_query() { "tree_sitter_query_project", r#" $ tree-sitter-grep -q '(capture) @c' --language tree-sitter-query - example.scm:1:(function_item) @f + example.scm:(function_item) @f "#, ); } @@ -368,7 +368,7 @@ fn test_tree_sitter_query_auto_language() { "tree_sitter_query_project", r#" $ tree-sitter-grep -q '(capture) @c' - example.scm:1:(function_item) @f + example.scm:(function_item) @f "#, ); } @@ -379,7 +379,7 @@ fn test_json() { "json_project", r#" $ tree-sitter-grep -q '(string_content) @c' --language json - example.json:2: "hello": "ok" + example.json: "hello": "ok" "#, ); } @@ -390,7 +390,7 @@ fn test_json_auto_language() { "json_project", r#" $ tree-sitter-grep -q '(string_content) @c' - example.json:2: "hello": "ok" + example.json: "hello": "ok" "#, ); } @@ -401,7 +401,7 @@ fn test_css() { "css_project", r#" $ tree-sitter-grep -q '(tag_name) @c' --language css - example.css:1:h1 { + example.css:h1 { "#, ); } @@ -412,7 +412,7 @@ fn test_css_auto_language() { "css_project", r#" $ tree-sitter-grep -q '(tag_name) @c' - example.css:1:h1 { + example.css:h1 { "#, ); } @@ -423,7 +423,7 @@ fn test_lua() { "lua_project", r#" $ tree-sitter-grep -q '(identifier) @c' --language lua - example.lua:1:function hello() + example.lua:function hello() "#, ); } @@ -434,7 +434,7 @@ fn test_lua_auto_language() { "lua_project", r#" $ tree-sitter-grep -q '(identifier) @c' - example.lua:1:function hello() + example.lua:function hello() "#, ); } diff --git a/tests/output.rs b/tests/output.rs index ef7b911..cc30d4b 100644 --- a/tests/output.rs +++ b/tests/output.rs @@ -11,15 +11,15 @@ fn test_query_inline() { "rust_project", r#" $ tree-sitter-grep --query '(function_item) @function_item' --language rust - src/helpers.rs:1:pub fn helper() {} - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/stop.rs:1:fn stop_it() {} + src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/stop.rs:fn stop_it() {} "#, ); } @@ -30,15 +30,15 @@ fn test_query_inline_short_option() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @function_item' --language rust - src/helpers.rs:1:pub fn helper() {} - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/stop.rs:1:fn stop_it() {} + src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/stop.rs:fn stop_it() {} "#, ); } @@ -63,15 +63,15 @@ fn test_query_file() { "rust_project", r#" $ tree-sitter-grep --query-file ./function-item.scm --language rust - src/helpers.rs:1:pub fn helper() {} - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/stop.rs:1:fn stop_it() {} + src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/stop.rs:fn stop_it() {} "#, ); } @@ -82,15 +82,15 @@ fn test_query_file_short_option() { "rust_project", r#" $ tree-sitter-grep -Q ./function-item.scm --language rust - src/helpers.rs:1:pub fn helper() {} - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/stop.rs:1:fn stop_it() {} + src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/stop.rs:fn stop_it() {} "#, ); } @@ -101,13 +101,13 @@ fn test_specify_single_file() { "rust_project", r#" $ tree-sitter-grep --query '(function_item) @function_item' --language rust src/lib.rs - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } + pub fn add(left: usize, right: usize) -> usize { + left + right + } + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } "#, ); } @@ -117,14 +117,14 @@ fn test_specify_single_file_preserves_leading_dot_slash() { assert_sorted_output( "rust_project", r#" - $ tree-sitter-grep --query '(function_item) @function_item' --language rust ./src/lib.rs - ./src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - ./src/lib.rs:4: left + right - ./src/lib.rs:5:} - ./src/lib.rs:12: fn it_works() { - ./src/lib.rs:13: let result = add(2, 2); - ./src/lib.rs:14: assert_eq!(result, 4); - ./src/lib.rs:15: } + $ tree-sitter-grep --query '(function_item) @function_item' --language rust --with-filename ./src/lib.rs + ./src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + ./src/lib.rs: left + right + ./src/lib.rs:} + ./src/lib.rs: fn it_works() { + ./src/lib.rs: let result = add(2, 2); + ./src/lib.rs: assert_eq!(result, 4); + ./src/lib.rs: } "#, ); } @@ -135,14 +135,14 @@ fn test_specify_multiple_files() { "rust_project", r#" $ tree-sitter-grep --query '(function_item) @function_item' --language rust src/lib.rs ./src/helpers.rs - ./src/helpers.rs:1:pub fn helper() {} - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } + ./src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } "#, ); } @@ -218,15 +218,15 @@ fn test_auto_language_single_known_language_encountered() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @function_item' - src/helpers.rs:1:pub fn helper() {} - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/stop.rs:1:fn stop_it() {} + src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/stop.rs:fn stop_it() {} "#, ); } @@ -237,8 +237,8 @@ fn test_auto_language_multiple_parseable_languages() { "mixed_project", r#" $ tree-sitter-grep -q '(arrow_function) @arrow_function' - javascript_src/index.js:1:const js_foo = () => {} - typescript_src/index.tsx:1:const foo = () => {} + javascript_src/index.js:const js_foo = () => {} + typescript_src/index.tsx:const foo = () => {} "#, ); } @@ -249,7 +249,7 @@ fn test_auto_language_single_parseable_languages() { "mixed_project", r#" $ tree-sitter-grep -q '(function_item) @function_item' - rust_src/lib.rs:1:fn foo() {} + rust_src/lib.rs:fn foo() {} "#, ); } @@ -260,15 +260,15 @@ fn test_capture_name() { "rust_project", r#" $ tree-sitter-grep -q '(function_item name: (identifier) @name) @function_item' --language rust --capture function_item - src/helpers.rs:1:pub fn helper() {} - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/stop.rs:1:fn stop_it() {} + src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/stop.rs:fn stop_it() {} "#, ); } @@ -279,9 +279,9 @@ fn test_predicate() { "rust_project", r#" $ tree-sitter-grep -q '(function_item name: (identifier) @name (#eq? @name "add")) @function_item' --language rust --capture function_item - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} "#, ); } @@ -317,7 +317,7 @@ fn test_unknown_option() { tip: a similar argument exists: '--query' - Usage: tree-sitter-grep <--query-file |--query |--filter > |--query |--capture |--language |--filter |--filter-arg |--vimgrep|--after-context |--before-context |--context |--only-matching|--byte-offset> + Usage: tree-sitter-grep <--query-file |--query |--filter > |--query |--capture |--language |--filter |--filter-arg |--vimgrep|--after-context |--before-context |--context |--only-matching|--byte-offset|--colors |--color |--pretty|--heading|--no-heading|--with-filename|--no-filename|--line-number|--no-line-number|--column|--no-column> For more information, try '--help'. "#, @@ -332,11 +332,11 @@ fn test_filter_plugin() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @function_item' --language rust --filter ../../../target/debug/examples/libfilter_before_line_10.so - src/helpers.rs:1:pub fn helper() {} - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/stop.rs:1:fn stop_it() {} + src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/stop.rs:fn stop_it() {} "#, ); } @@ -349,8 +349,8 @@ fn test_filter_plugin_with_argument() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @function_item' --language rust --filter ../../../target/debug/examples/libfilter_before_line_number.so --filter-arg 2 - src/helpers.rs:1:pub fn helper() {} - src/stop.rs:1:fn stop_it() {} + src/helpers.rs:pub fn helper() {} + src/stop.rs:fn stop_it() {} "#, ); } @@ -389,11 +389,11 @@ fn test_filter_plugin_no_query() { "rust_project", r#" $ tree-sitter-grep --language rust --filter ../../../target/debug/examples/libfilter_function_items_before_line_10.so - src/helpers.rs:1:pub fn helper() {} - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/stop.rs:1:fn stop_it() {} + src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/stop.rs:fn stop_it() {} "#, ); } @@ -490,6 +490,120 @@ fn test_help_option() { If -o (--only-matching) is specified, print the offset of the matching part itself. + --colors + This flag specifies color settings for use in the output. + + This flag may be provided multiple times. Settings are applied iteratively. Colors are + limited to one of eight choices: red, blue, green, cyan, magenta, yellow, white and black. + Styles are limited to nobold, bold, nointense, intense, nounderline or underline. + + The format of the flag is '{type}:{attribute}:{value}'. '{type}' should be one of path, + line, column or match. '{attribute}' can be fg, bg or style. '{value}' is either a color + (for fg and bg) or a text style. A special format, '{type}:none', will clear all color + settings for '{type}'. + + For example, the following command will change the match color to magenta and the + background color for line numbers to yellow: + + tree-sitter-grep --colors 'match:fg:magenta' --colors 'line:bg:yellow' -q '(function_item) + @f' + + Extended colors can be used for '{value}' when the terminal supports ANSI color sequences. + These are specified as either 'x' (256-color) or 'x,x,x' (24-bit truecolor) where x is a + number between 0 and 255 inclusive. x may be given as a normal decimal number or a + hexadecimal number, which is prefixed by `0x`. + + For example, the following command will change the match background color to that + represented by the rgb value (0,128,255): + + tree-sitter-grep --colors 'match:bg:0,128,255' + + or, equivalently, + + tree-sitter-grep --colors 'match:bg:0x0,0x80,0xFF' + + Note that the intense and nointense style flags will have no effect when used alongside + these extended color codes. + + --color + This flag controls when to use colors. + + The default setting is 'auto', which means tree-sitter-grep will try to guess when to use + colors. For example, if tree-sitter-grep is printing to a terminal, then it will use + colors, but if it is redirected to a file or a pipe, then it will suppress color output. + tree-sitter-grep will suppress color output in some other circumstances as well. For + example, if the TERM environment variable is not set or set to 'dumb', then + tree-sitter-grep will not use colors. + + When the --vimgrep flag is given to tree-sitter-grep, then the default value for the + --color flag changes to 'never'. + + Possible values: + - never: Colors will never be used + - auto: The default. tree-sitter-grep tries to be smart + - always: Colors will always be used regardless of where output is sent + - ansi: Like 'always', but emits ANSI escapes (even in a Windows console) + + -p, --pretty + This is a convenience alias for '--color always --heading --line-number'. + + This flag is useful when you still want pretty output even if you're piping + tree-sitter-grep to another program or file. For example: 'tree-sitter-grep -p -q + "(function_item) @c" | less -R'. + + --heading + This flag prints the file path above clusters of matches from each file instead of + printing the file path as a prefix for each matched line. + + This is the default mode when printing to a terminal. + + This overrides the --no-heading flag. + + --no-heading + Don't group matches by each file. + + If --no-heading is provided in addition to the -H/--with-filename flag, then file paths + will be printed as a prefix for every matched line. This is the default mode when not + printing to a terminal. + + This overrides the --heading flag. + + -H, --with-filename + Display the file path for matches. + + This is the default when more than one file is searched. If --heading is enabled (the + default when printing to a terminal), the file path will be shown above clusters of + matches from each file; otherwise, the file name will be shown as a prefix for each + matched line. + + This flag overrides --no-filename. + + -I, --no-filename + Never print the file path with the matched lines. + + This is the default when tree-sitter-grep is explicitly instructed to search one file or + stdin. + + This flag overrides --with-filename. + + -n, --line-number + Show line numbers (1-based). + + This is enabled by default when searching in a terminal. + + -N, --no-line-number + Suppress line numbers. + + This is enabled by default when not searching in a terminal. + + --column + Show column numbers (1-based). + + This only shows the column numbers for the first match on each line. This does not try to + account for Unicode. One byte is equal to one column. This implies --line-number. + + This flag can be disabled with --no-column. + -h, --help Print help (see a summary with '-h') "#, @@ -536,6 +650,27 @@ fn test_help_short_option() { separate output line -b, --byte-offset Print the 0-based byte offset within the input file before each line of output + --colors + This flag specifies color settings for use in the output + --color + This flag controls when to use colors [possible values: never, auto, always, ansi] + -p, --pretty + This is a convenience alias for '--color always --heading --line-number' + --heading + This flag prints the file path above clusters of matches from each file instead of + printing the file path as a prefix for each matched line + --no-heading + Don't group matches by each file + -H, --with-filename + Display the file path for matches + -I, --no-filename + Never print the file path with the matched lines + -n, --line-number + Show line numbers (1-based) + -N, --no-line-number + Suppress line numbers + --column + Show column numbers (1-based) -h, --help Print help (see more with '--help') "#, @@ -582,9 +717,9 @@ fn test_macro_contents() { "match_inside_macro", r#" $ tree-sitter-grep -q '(call_expression) @c' -l rust - foo.rs:4: self.factory - foo.rs:5: .create_parameter_declaration("whee", Option::>::None) - foo.rs:6: .wrap(), + foo.rs: self.factory + foo.rs: .create_parameter_declaration("whee", Option::>::None) + foo.rs: .wrap(), "#, ); } @@ -607,11 +742,11 @@ fn test_overlapping_matches() { "rust_overlapping", r#" $ tree-sitter-grep -q '(closure_expression) @closure_expression' --language rust - src/lib.rs:2: let f = || { - src/lib.rs:3: || { - src/lib.rs:4: println!("whee"); - src/lib.rs:5: } - src/lib.rs:6: }; + src/lib.rs: let f = || { + src/lib.rs: || { + src/lib.rs: println!("whee"); + src/lib.rs: } + src/lib.rs: }; "#, ); } @@ -634,20 +769,22 @@ fn test_after_context() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @f' -l rust --after-context 2 - src/stop.rs:1:fn stop_it() {} - src/helpers.rs:1:pub fn helper() {} - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs-6- - src/lib.rs-7-#[cfg(test)] + src/stop.rs:fn stop_it() {} -- - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/lib.rs-16-} - src/lib.rs-17- + src/helpers.rs:pub fn helper() {} + -- + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs- + src/lib.rs-#[cfg(test)] + -- + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/lib.rs-} + src/lib.rs- "#, ); } @@ -658,10 +795,10 @@ fn test_after_context_matches_overlap_context_lines() { "rust_overlapping", r#" $ tree-sitter-grep -q '(call_expression function: (identifier) @function_name (#match? @function_name "^h"))' -l rust -A 2 - src/lib.rs:10: hello(); - src/lib.rs:11: hoo(); - src/lib.rs-12- raa(); - src/lib.rs-13- roo(); + src/lib.rs: hello(); + src/lib.rs: hoo(); + src/lib.rs- raa(); + src/lib.rs- roo(); "#, ); } @@ -672,13 +809,13 @@ fn test_after_context_overlapping_matches() { "rust_overlapping", r#" $ tree-sitter-grep -q '(closure_expression) @c' -l rust --after-context 2 - src/lib.rs:2: let f = || { - src/lib.rs:3: || { - src/lib.rs:4: println!("whee"); - src/lib.rs:5: } - src/lib.rs:6: }; - src/lib.rs-7-} - src/lib.rs-8- + src/lib.rs: let f = || { + src/lib.rs: || { + src/lib.rs: println!("whee"); + src/lib.rs: } + src/lib.rs: }; + src/lib.rs-} + src/lib.rs- "#, ); } @@ -703,20 +840,22 @@ fn test_after_context_short_option() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @f' -l rust -A 2 - src/stop.rs:1:fn stop_it() {} - src/helpers.rs:1:pub fn helper() {} - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs-6- - src/lib.rs-7-#[cfg(test)] + src/stop.rs:fn stop_it() {} -- - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/lib.rs-16-} - src/lib.rs-17- + src/helpers.rs:pub fn helper() {} + -- + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs- + src/lib.rs-#[cfg(test)] + -- + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/lib.rs-} + src/lib.rs- "#, ); } @@ -727,21 +866,23 @@ fn test_before_context() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @f' -l rust --before-context 3 - src/stop.rs:1:fn stop_it() {} - src/helpers.rs:1:pub fn helper() {} - src/lib.rs-1-mod helpers; - src/lib.rs-2- - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} + src/stop.rs:fn stop_it() {} -- - src/lib.rs-9- use super::*; - src/lib.rs-10- - src/lib.rs-11- #[test] - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } + src/helpers.rs:pub fn helper() {} + -- + src/lib.rs-mod helpers; + src/lib.rs- + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + -- + src/lib.rs- use super::*; + src/lib.rs- + src/lib.rs- #[test] + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } "#, ); } @@ -752,21 +893,23 @@ fn test_before_context_short_option() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @f' -l rust -B 3 - src/stop.rs:1:fn stop_it() {} - src/helpers.rs:1:pub fn helper() {} - src/lib.rs-1-mod helpers; - src/lib.rs-2- - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} + src/stop.rs:fn stop_it() {} -- - src/lib.rs-9- use super::*; - src/lib.rs-10- - src/lib.rs-11- #[test] - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } + src/helpers.rs:pub fn helper() {} + -- + src/lib.rs-mod helpers; + src/lib.rs- + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + -- + src/lib.rs- use super::*; + src/lib.rs- + src/lib.rs- #[test] + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } "#, ); } @@ -777,10 +920,10 @@ fn test_before_context_matches_overlap_context_lines() { "rust_overlapping", r#" $ tree-sitter-grep -q '(call_expression function: (identifier) @function_name (#match? @function_name "^h"))' -l rust -B 2 - src/lib.rs-8- - src/lib.rs-9-fn something_else() { - src/lib.rs:10: hello(); - src/lib.rs:11: hoo(); + src/lib.rs- + src/lib.rs-fn something_else() { + src/lib.rs: hello(); + src/lib.rs: hoo(); "#, ); } @@ -791,13 +934,13 @@ fn test_before_context_overlapping_matches() { "rust_overlapping_with_preceding_lines", r#" $ tree-sitter-grep -q '(closure_expression) @c' -l rust --before-context 2 - src/lib.rs-5- .i_promise() - src/lib.rs-6- .but_it_has_to_be_longer(); - src/lib.rs:7: let f = || { - src/lib.rs:8: || { - src/lib.rs:9: println!("whee"); - src/lib.rs:10: } - src/lib.rs:11: }; + src/lib.rs- .i_promise() + src/lib.rs- .but_it_has_to_be_longer(); + src/lib.rs: let f = || { + src/lib.rs: || { + src/lib.rs: println!("whee"); + src/lib.rs: } + src/lib.rs: }; "#, ); } @@ -822,24 +965,26 @@ fn test_context() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @f' -l rust --context 2 - src/stop.rs:1:fn stop_it() {} - src/helpers.rs:1:pub fn helper() {} - src/lib.rs-1-mod helpers; - src/lib.rs-2- - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs-6- - src/lib.rs-7-#[cfg(test)] + src/stop.rs:fn stop_it() {} -- - src/lib.rs-10- - src/lib.rs-11- #[test] - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/lib.rs-16-} - src/lib.rs-17- + src/helpers.rs:pub fn helper() {} + -- + src/lib.rs-mod helpers; + src/lib.rs- + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs- + src/lib.rs-#[cfg(test)] + -- + src/lib.rs- + src/lib.rs- #[test] + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/lib.rs-} + src/lib.rs- "#, ); } @@ -850,26 +995,28 @@ fn test_context_adjacent_after_and_before_context_lines() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @f' -l rust --context 3 - src/stop.rs:1:fn stop_it() {} - src/helpers.rs:1:pub fn helper() {} - src/lib.rs-1-mod helpers; - src/lib.rs-2- - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs-6- - src/lib.rs-7-#[cfg(test)] - src/lib.rs-8-mod tests { - src/lib.rs-9- use super::*; - src/lib.rs-10- - src/lib.rs-11- #[test] - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/lib.rs-16-} - src/lib.rs-17- - src/lib.rs-18-mod stop; + src/stop.rs:fn stop_it() {} + -- + src/helpers.rs:pub fn helper() {} + -- + src/lib.rs-mod helpers; + src/lib.rs- + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs- + src/lib.rs-#[cfg(test)] + src/lib.rs-mod tests { + src/lib.rs- use super::*; + src/lib.rs- + src/lib.rs- #[test] + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/lib.rs-} + src/lib.rs- + src/lib.rs-mod stop; "#, ); } @@ -880,26 +1027,28 @@ fn test_context_overlapping_after_and_before_context_lines() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @f' -l rust --context 4 - src/stop.rs:1:fn stop_it() {} - src/helpers.rs:1:pub fn helper() {} - src/lib.rs-1-mod helpers; - src/lib.rs-2- - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs-6- - src/lib.rs-7-#[cfg(test)] - src/lib.rs-8-mod tests { - src/lib.rs-9- use super::*; - src/lib.rs-10- - src/lib.rs-11- #[test] - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/lib.rs-16-} - src/lib.rs-17- - src/lib.rs-18-mod stop; + src/stop.rs:fn stop_it() {} + -- + src/helpers.rs:pub fn helper() {} + -- + src/lib.rs-mod helpers; + src/lib.rs- + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs- + src/lib.rs-#[cfg(test)] + src/lib.rs-mod tests { + src/lib.rs- use super::*; + src/lib.rs- + src/lib.rs- #[test] + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/lib.rs-} + src/lib.rs- + src/lib.rs-mod stop; "#, ); } @@ -910,24 +1059,26 @@ fn test_context_short_option() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @f' -l rust -C 2 - src/stop.rs:1:fn stop_it() {} - src/helpers.rs:1:pub fn helper() {} - src/lib.rs-1-mod helpers; - src/lib.rs-2- - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs-6- - src/lib.rs-7-#[cfg(test)] + src/stop.rs:fn stop_it() {} -- - src/lib.rs-10- - src/lib.rs-11- #[test] - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/lib.rs-16-} - src/lib.rs-17- + src/helpers.rs:pub fn helper() {} + -- + src/lib.rs-mod helpers; + src/lib.rs- + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs- + src/lib.rs-#[cfg(test)] + -- + src/lib.rs- + src/lib.rs- #[test] + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/lib.rs-} + src/lib.rs- "#, ); } @@ -938,22 +1089,24 @@ fn test_before_and_after_context() { "rust_project", r#" $ tree-sitter-grep -q '(function_item) @f' -l rust --before-context 2 --after-context 1 - src/stop.rs:1:fn stop_it() {} - src/helpers.rs:1:pub fn helper() {} - src/lib.rs-1-mod helpers; - src/lib.rs-2- - src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4: left + right - src/lib.rs:5:} - src/lib.rs-6- + src/stop.rs:fn stop_it() {} -- - src/lib.rs-10- - src/lib.rs-11- #[test] - src/lib.rs:12: fn it_works() { - src/lib.rs:13: let result = add(2, 2); - src/lib.rs:14: assert_eq!(result, 4); - src/lib.rs:15: } - src/lib.rs-16-} + src/helpers.rs:pub fn helper() {} + -- + src/lib.rs-mod helpers; + src/lib.rs- + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs- + -- + src/lib.rs- + src/lib.rs- #[test] + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/lib.rs-} "#, ); } @@ -1074,8 +1227,8 @@ fn test_only_matching() { "rust_project", r#" $ tree-sitter-grep -q '(parameter) @c' --language rust --only-matching - src/lib.rs:3:left: usize - src/lib.rs:3:right: usize + src/lib.rs:left: usize + src/lib.rs:right: usize "#, ); } @@ -1086,8 +1239,8 @@ fn test_only_matching_short_option() { "rust_project", r#" $ tree-sitter-grep -q '(parameter) @c' --language rust -o - src/lib.rs:3:left: usize - src/lib.rs:3:right: usize + src/lib.rs:left: usize + src/lib.rs:right: usize "#, ); } @@ -1098,11 +1251,11 @@ fn test_only_matching_multiline_overlapping_matches() { "rust_overlapping", r#" $ tree-sitter-grep -q '(closure_expression) @c' -l rust --only-matching - src/lib.rs:2:|| { - src/lib.rs:3: || { - src/lib.rs:4: println!("whee"); - src/lib.rs:5: } - src/lib.rs:6: } + src/lib.rs:|| { + src/lib.rs: || { + src/lib.rs: println!("whee"); + src/lib.rs: } + src/lib.rs: } "#, ); } @@ -1113,10 +1266,10 @@ fn test_only_matching_multiline_overlapping_matches_starting_on_same_line() { "rust_overlapping_start_same_line", r#" $ tree-sitter-grep -q '(closure_expression) @c' -l rust --only-matching - src/lib.rs:3:|| { || { - src/lib.rs:4: println!("whee"); - src/lib.rs:5: } - src/lib.rs:6: } + src/lib.rs:|| { || { + src/lib.rs: println!("whee"); + src/lib.rs: } + src/lib.rs: } "#, ); } @@ -1138,15 +1291,15 @@ fn test_byte_offset() { "rust_project_byte_offset", r#" $ tree-sitter-grep -q '(function_item) @function_item' -l rust --byte-offset - src/helpers.rs:1:0:pub fn helper() {} - src/stop.rs:1:0:fn stop_it() {} - src/lib.rs:3:14:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4:63: left + right - src/lib.rs:5:80:} - src/lib.rs:12:139: fn it_works() { - src/lib.rs:13:159: let result = add(2, 2); - src/lib.rs:14:191: assert_eq!(result, 4); - src/lib.rs:15:222: } + src/helpers.rs:0:pub fn helper() {} + src/stop.rs:0:fn stop_it() {} + src/lib.rs:14:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs:63: left + right + src/lib.rs:80:} + src/lib.rs:139: fn it_works() { + src/lib.rs:159: let result = add(2, 2); + src/lib.rs:191: assert_eq!(result, 4); + src/lib.rs:222: } "#, ); } @@ -1157,15 +1310,15 @@ fn test_byte_offset_short_option() { "rust_project_byte_offset", r#" $ tree-sitter-grep -q '(function_item) @function_item' -l rust -b - src/helpers.rs:1:0:pub fn helper() {} - src/stop.rs:1:0:fn stop_it() {} - src/lib.rs:3:14:pub fn add(left: usize, right: usize) -> usize { - src/lib.rs:4:63: left + right - src/lib.rs:5:80:} - src/lib.rs:12:139: fn it_works() { - src/lib.rs:13:159: let result = add(2, 2); - src/lib.rs:14:191: assert_eq!(result, 4); - src/lib.rs:15:222: } + src/helpers.rs:0:pub fn helper() {} + src/stop.rs:0:fn stop_it() {} + src/lib.rs:14:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs:63: left + right + src/lib.rs:80:} + src/lib.rs:139: fn it_works() { + src/lib.rs:159: let result = add(2, 2); + src/lib.rs:191: assert_eq!(result, 4); + src/lib.rs:222: } "#, ); } @@ -1190,8 +1343,358 @@ fn test_byte_offset_only_matching() { "rust_project_byte_offset", r#" $ tree-sitter-grep -q '(parameter) @c' -l rust --byte-offset --only-matching - src/lib.rs:3:25:left: usize - src/lib.rs:3:38:right: usize + src/lib.rs:25:left: usize + src/lib.rs:38:right: usize + "#, + ); +} + +#[test] +fn test_line_number() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @f' -l rust --line-number + src/helpers.rs:1:pub fn helper() {} + src/stop.rs:1:fn stop_it() {} + src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs:4: left + right + src/lib.rs:5:} + src/lib.rs:12: fn it_works() { + src/lib.rs:13: let result = add(2, 2); + src/lib.rs:14: assert_eq!(result, 4); + src/lib.rs:15: } + "#, + ); +} + +#[test] +fn test_line_number_context() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @f' -l rust --line-number -A 1 + src/stop.rs:1:fn stop_it() {} + -- + src/helpers.rs:1:pub fn helper() {} + -- + src/lib.rs:3:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs:4: left + right + src/lib.rs:5:} + src/lib.rs-6- + -- + src/lib.rs:12: fn it_works() { + src/lib.rs:13: let result = add(2, 2); + src/lib.rs:14: assert_eq!(result, 4); + src/lib.rs:15: } + src/lib.rs-16-} + "#, + ); +} + +#[test] +fn test_no_filename() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @f' -l rust --no-filename + fn stop_it() {} + pub fn helper() {} + pub fn add(left: usize, right: usize) -> usize { + left + right + } + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } + "#, + ); +} + +#[test] +fn test_no_filename_short_option() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @f' -l rust -I + fn stop_it() {} + pub fn helper() {} + pub fn add(left: usize, right: usize) -> usize { + left + right + } + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } + "#, + ); +} + +#[test] +fn test_no_filename_context() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @f' -l rust --no-filename -A 1 + pub fn helper() {} + -- + fn stop_it() {} + -- + pub fn add(left: usize, right: usize) -> usize { + left + right + } + + -- + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } + } + "#, + ); +} + +#[test] +fn test_with_filename_single_file() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @c' -l rust --with-filename src/lib.rs + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + "#, + ); +} + +#[test] +fn test_with_filename_short_option_single_file() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @c' -l rust -H src/lib.rs + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + "#, + ); +} + +#[test] +fn test_with_filename_context_single_file() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @c' -l rust --with-filename -A 1 src/lib.rs + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs- + -- + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/lib.rs-} + "#, + ); +} + +#[test] +fn test_no_option_overrides_preceding_yes_option() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @f' -l rust --with-filename --no-filename + fn stop_it() {} + pub fn helper() {} + pub fn add(left: usize, right: usize) -> usize { + left + right + } + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } + "#, + ); +} + +#[test] +fn test_yes_option_overrides_preceding_no_option() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @f' -l rust --no-filename --with-filename + src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/stop.rs:fn stop_it() {} + "#, + ); +} + +#[test] +fn test_column() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @c' -l rust --column + src/stop.rs:1:1:fn stop_it() {} + src/helpers.rs:1:1:pub fn helper() {} + src/lib.rs:3:1:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs:4:1: left + right + src/lib.rs:5:1:} + src/lib.rs:12:5: fn it_works() { + src/lib.rs:13:5: let result = add(2, 2); + src/lib.rs:14:5: assert_eq!(result, 4); + src/lib.rs:15:5: } + "#, + ); +} + +#[test] +fn test_column_context() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @c' -l rust --column -A 1 + src/stop.rs:1:1:fn stop_it() {} + -- + src/helpers.rs:1:1:pub fn helper() {} + -- + src/lib.rs:3:1:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs:4:1: left + right + src/lib.rs:5:1:} + src/lib.rs-6- + -- + src/lib.rs:12:5: fn it_works() { + src/lib.rs:13:5: let result = add(2, 2); + src/lib.rs:14:5: assert_eq!(result, 4); + src/lib.rs:15:5: } + src/lib.rs-16-} + "#, + ); +} + +#[test] +fn test_no_column() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @c' -l rust --no-column + src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + src/stop.rs:fn stop_it() {} + "#, + ); +} + +#[test] +fn test_heading() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @c' -l rust --heading + src/helpers.rs + pub fn helper() {} + + src/stop.rs + fn stop_it() {} + + src/lib.rs + pub fn add(left: usize, right: usize) -> usize { + left + right + } + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } + "#, + ); +} + +#[test] +fn test_pretty() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @c' -l rust --pretty + src/stop.rs + 1:fn stop_it() {} + + src/helpers.rs + 1:pub fn helper() {} + + src/lib.rs + 3:pub fn add(left: usize, right: usize) -> usize { + 4: left + right + 5:} + 12: fn it_works() { + 13: let result = add(2, 2); + 14: assert_eq!(result, 4); + 15: } + "#, + ); +} + +#[test] +fn test_color_always() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @c' -l rust --color always + src/stop.rs:fn stop_it() {} + src/helpers.rs:pub fn helper() {} + src/lib.rs:pub fn add(left: usize, right: usize) -> usize { + src/lib.rs: left + right + src/lib.rs:} + src/lib.rs: fn it_works() { + src/lib.rs: let result = add(2, 2); + src/lib.rs: assert_eq!(result, 4); + src/lib.rs: } + "#, + ); +} + +#[test] +fn test_colors() { + assert_sorted_output( + "rust_project", + r#" + $ tree-sitter-grep -q '(function_item) @f' --pretty --colors 'match:fg:magenta' --colors 'line:bg:cyan' --colors 'path:fg:blue' + src/stop.rs + 1:fn stop_it() {} + + src/helpers.rs + 1:pub fn helper() {} + + src/lib.rs + 3:pub fn add(left: usize, right: usize) -> usize { + 4: left + right + 5:} + 12: fn it_works() { + 13: let result = add(2, 2); + 14: assert_eq!(result, 4); + 15: } "#, ); } diff --git a/tests/shared.rs b/tests/shared.rs index d4b5d31..95ab2ef 100644 --- a/tests/shared.rs +++ b/tests/shared.rs @@ -4,6 +4,7 @@ use std::{borrow::Cow, env, path::PathBuf, process::Command}; use assert_cmd::prelude::*; use predicates::prelude::*; use regex::Captures; +use text_diff::print_diff; #[macro_export] macro_rules! regex { @@ -141,11 +142,11 @@ fn massage_windows_line(line: &str) -> String { } fn strip_trailing_carriage_return(line: &str) -> Cow<'_, str> { - regex!(r#"\r$"#).replace(line, "") + regex!(r#"\r((?:\u{1b}\[\d+m)*)$"#).replace(line, "$1") } fn normalize_match_path(line: &str) -> Cow<'_, str> { - regex!(r#"^[^:]+[:-]\d+[:-]"#) + regex!(r#"^(?:\u{1b}\[\d+m)*[a-zA-Z_\-\\/]+\.(?:rs|js|tsx)"#) .replace(line, |captures: &Captures| captures[0].replace('\\', "/")) } @@ -193,6 +194,9 @@ pub fn assert_non_match_output(fixture_dir_name: &str, command_and_output: &str) .success() .stdout(predicate::function(|stdout: &str| { let stdout = massage_error_output(stdout); + if stdout != output { + print_diff(&stdout, &output, " "); + } stdout == output })); }