@ -134,8 +134,6 @@ pub struct LanguageConfiguration {
#[ serde(skip) ]
pub ( crate ) indent_query : OnceCell < Option < Query > > ,
#[ serde(skip) ]
pub ( crate ) textobject_query : OnceCell < Option < TextObjectQuery > > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub debugger : Option < DebugAdapterConfig > ,
@ -534,11 +532,6 @@ impl FromStr for AutoPairConfig {
}
}
#[ derive(Debug) ]
pub struct TextObjectQuery {
pub query : Query ,
}
#[ derive(Debug) ]
pub enum CapturedNode < ' a > {
Single ( Node < ' a > ) ,
@ -586,97 +579,36 @@ impl<'a> CapturedNode<'a> {
/// This number can be increased if new syntax highlight breakages are found, as long as the performance penalty is not too high.
const TREE_SITTER_MATCH_LIMIT : u32 = 256 ;
impl TextObjectQuery {
/// Run the query on the given node and return sub nodes which match given
/// capture ("function.inside", "class.around", etc).
///
/// Captures may contain multiple nodes by using quantifiers (+, *, etc),
/// and support for this is partial and could use improvement.
///
/// ```query
/// (comment)+ @capture
///
/// ; OR
/// (
/// (comment)*
/// .
/// (function)
/// ) @capture
/// ```
pub fn capture_nodes < ' a > (
& ' a self ,
capture_name : & str ,
node : Node < ' a > ,
slice : RopeSlice < ' a > ,
cursor : & ' a mut QueryCursor ,
) -> Option < impl Iterator < Item = CapturedNode < ' a > > > {
self . capture_nodes_any ( & [ capture_name ] , node , slice , cursor )
}
/// Find the first capture that exists out of all given `capture_names`
/// and return sub nodes that match this capture.
pub fn capture_nodes_any < ' a > (
& ' a self ,
capture_names : & [ & str ] ,
node : Node < ' a > ,
slice : RopeSlice < ' a > ,
cursor : & ' a mut QueryCursor ,
) -> Option < impl Iterator < Item = CapturedNode < ' a > > > {
let capture_idx = capture_names
. iter ( )
. find_map ( | cap | self . query . capture_index_for_name ( cap ) ) ? ;
cursor . set_match_limit ( TREE_SITTER_MATCH_LIMIT ) ;
let nodes = cursor
. captures ( & self . query , node , RopeProvider ( slice ) )
. filter_map ( move | ( mat , _ ) | {
let nodes : Vec < _ > = mat
. captures
. iter ( )
. filter_map ( | cap | ( cap . index = = capture_idx ) . then_some ( cap . node ) )
. collect ( ) ;
if nodes . len ( ) > 1 {
Some ( CapturedNode ::Grouped ( nodes ) )
} else {
nodes . into_iter ( ) . map ( CapturedNode ::Single ) . next ( )
}
} ) ;
Some ( nodes )
}
}
pub fn read_query ( language : & str , filename : & str ) -> String {
pub fn read_query ( language : & str , filename : & str ) -> Option < String > {
static INHERITS_REGEX : Lazy < Regex > =
Lazy ::new ( | | Regex ::new ( r";+\s*inherits\s*:?\s*([a-z_,()-]+)\s*" ) . unwrap ( ) ) ;
let query = load_runtime_file ( language , filename ) . unwrap_or_default( ) ;
let query = load_runtime_file ( language , filename ) . ok ( ) ? ;
// replaces all "; inherits <language>(,<language>)*" with the queries of the given language(s)
INHERITS_REGEX
let contents = INHERITS_REGEX
. replace_all ( & query , | captures : & regex ::Captures | {
captures [ 1 ]
. split ( ',' )
. map( | language | format! ( "\n{}\n" , read_query ( language , filename ) ) )
. filter_map ( | language | Some ( format! ( "\n{}\n" , read_query ( language , filename ) ? ) ) )
. collect ::< String > ( )
} )
. to_string ( )
. to_string ( ) ;
Some ( contents )
}
impl LanguageConfiguration {
fn initialize_highlight ( & self , scopes : & [ String ] ) -> Option < Arc < HighlightConfiguration > > {
let highlights_query = read_query ( & self . language_id , "highlights.scm" ) ;
let highlights_query = read_query ( & self . language_id , "highlights.scm" ) ? ;
// always highlight syntax errors
// highlights_query += "\n(ERROR) @error";
let textobjects_query = read_query ( & self . language_id , "textobjects.scm" ) ;
let injections_query = read_query ( & self . language_id , "injections.scm" ) ;
let locals_query = read_query ( & self . language_id , "locals.scm" ) ;
if highlights_query . is_empty ( ) {
None
} else {
let language = get_language ( self . grammar . as_deref ( ) . unwrap_or ( & self . language_id ) )
. map_err ( | err | {
log ::error ! (
@ -689,8 +621,9 @@ impl LanguageConfiguration {
let config = HighlightConfiguration ::new (
language ,
& highlights_query ,
& injections_query ,
& locals_query ,
textobjects_query . as_deref ( ) ,
& injections_query . unwrap_or_default ( ) ,
& locals_query . unwrap_or_default ( ) ,
)
. map_err ( | err | log ::error ! ( "Could not parse queries for language {:?}. Are your grammars out of sync? Try running 'hx --grammar fetch' and 'hx --grammar build'. This query could not be parsed: {:?}" , self . language_id , err ) )
. ok ( ) ? ;
@ -698,7 +631,6 @@ impl LanguageConfiguration {
config . configure ( scopes ) ;
Some ( Arc ::new ( config ) )
}
}
pub fn reconfigure ( & self , scopes : & [ String ] ) {
if let Some ( Some ( config ) ) = self . highlight_config . get ( ) {
@ -722,24 +654,12 @@ impl LanguageConfiguration {
. as_ref ( )
}
pub fn textobject_query ( & self ) -> Option < & TextObjectQuery > {
self . textobject_query
. get_or_init ( | | {
self . load_query ( "textobjects.scm" )
. map ( | query | TextObjectQuery { query } )
} )
. as_ref ( )
}
pub fn scope ( & self ) -> & str {
& self . scope
}
fn load_query ( & self , kind : & str ) -> Option < Query > {
let query_text = read_query ( & self . language_id , kind ) ;
if query_text . is_empty ( ) {
return None ;
}
let query_text = read_query ( & self . language_id , kind ) ? ;
let lang = self . highlight_config . get ( ) ? . as_ref ( ) ? . language ;
Query ::new ( lang , & query_text )
. map_err ( | e | {
@ -1457,6 +1377,42 @@ impl Syntax {
}
}
pub fn textobject_nodes < ' a > (
& ' a self ,
capture_names : & ' a [ & str ] ,
source : RopeSlice < ' a > ,
query_range : Option < std ::ops ::Range < usize > > ,
) -> impl Iterator < Item = CapturedNode < ' a > > {
self . query_iter (
| config | config . textobjects_query . as_ref ( ) ,
source ,
query_range ,
)
. filter_map ( move | ( layer , match_ , _ ) | {
// TODO: cache this per-language with a hashmap?
let capture_idx = capture_names . iter ( ) . find_map ( | name | {
layer
. config
. textobjects_query
. as_ref ( )
. expect ( "layer must have textobjects query in order to match" )
. capture_index_for_name ( name )
} ) ? ;
let nodes : Vec < _ > = match_
. captures
. iter ( )
. filter_map ( | cap | ( cap . index = = capture_idx ) . then_some ( cap . node ) )
. collect ( ) ;
if nodes . len ( ) > 1 {
Some ( CapturedNode ::Grouped ( nodes ) )
} else {
nodes . into_iter ( ) . map ( CapturedNode ::Single ) . next ( )
}
} )
}
pub fn tree_for_byte_range ( & self , start : usize , end : usize ) -> & Tree {
let mut container_id = self . root ;
@ -1748,7 +1704,8 @@ pub enum HighlightEvent {
#[ derive(Debug) ]
pub struct HighlightConfiguration {
pub language : Grammar ,
pub query : Query ,
query : Query ,
textobjects_query : Option < Query > ,
injections_query : Query ,
combined_injections_patterns : Vec < usize > ,
highlights_pattern_index : usize ,
@ -1846,6 +1803,7 @@ impl HighlightConfiguration {
pub fn new (
language : Grammar ,
highlights_query : & str ,
textobjects_query : Option < & str > ,
injection_query : & str ,
locals_query : & str ,
) -> Result < Self , QueryError > {
@ -1865,6 +1823,9 @@ impl HighlightConfiguration {
highlights_pattern_index + = 1 ;
}
}
let textobjects_query = textobjects_query
. map ( | source | Query ::new ( language , source ) )
. transpose ( ) ? ;
let injections_query = Query ::new ( language , injection_query ) ? ;
let combined_injections_patterns = ( 0 .. injections_query . pattern_count ( ) )
@ -1922,6 +1883,7 @@ impl HighlightConfiguration {
Ok ( Self {
language ,
query ,
textobjects_query ,
injections_query ,
combined_injections_patterns ,
highlights_pattern_index ,
@ -2809,11 +2771,7 @@ mod test {
. unwrap ( ) ;
let language = get_language ( "rust" ) . unwrap ( ) ;
let query = Query ::new ( language , query_str ) . unwrap ( ) ;
let textobject = TextObjectQuery { query } ;
let mut cursor = QueryCursor ::new ( ) ;
let config = HighlightConfiguration ::new ( language , "" , "" , "" ) . unwrap ( ) ;
let config = HighlightConfiguration ::new ( language , "" , Some ( query_str ) , "" , "" ) . unwrap ( ) ;
let syntax = Syntax ::new (
source . slice ( .. ) ,
Arc ::new ( config ) ,
@ -2821,11 +2779,10 @@ mod test {
)
. unwrap ( ) ;
let root = syntax . tree ( ) . root_node ( ) ;
let mut test = | capture , range | {
let matches : Vec < _ > = textobject
. capture_nodes ( capture , root , source . slice ( .. ) , & mut cursor )
. unwrap ( )
let test = | capture , range | {
let capture_names = & [ capture ] ;
let matches : Vec < _ > = syntax
. textobject_nodes ( capture_names , source . slice ( .. ) , None )
. collect ( ) ;
assert_eq! (
@ -2881,6 +2838,7 @@ mod test {
language ,
& std ::fs ::read_to_string ( "../runtime/grammars/sources/rust/queries/highlights.scm" )
. unwrap ( ) ,
None , // textobjects.scm
& std ::fs ::read_to_string ( "../runtime/grammars/sources/rust/queries/injections.scm" )
. unwrap ( ) ,
"" , // locals.scm
@ -2989,7 +2947,7 @@ mod test {
. unwrap ( ) ;
let language = get_language ( language_name ) . unwrap ( ) ;
let config = HighlightConfiguration ::new ( language , "" , "" , "" ) . unwrap ( ) ;
let config = HighlightConfiguration ::new ( language , "" , None , "" , "" ) . unwrap ( ) ;
let syntax = Syntax ::new (
source . slice ( .. ) ,
Arc ::new ( config ) ,