diff --git a/Cargo.lock b/Cargo.lock index 456e8f7..0b748a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,10 +91,11 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" dependencies = [ + "find-msvc-tools", "shlex", ] @@ -217,6 +218,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + [[package]] name = "foldhash" version = "0.1.5" @@ -225,9 +232,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", diff --git a/src/highlight.rs b/src/highlight.rs index aba37ee..dc67952 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -32,6 +32,8 @@ pub enum TokenKind { Special, /// A program constant or other statically-known name Constant, + /// A function call or some other active operation + Function, } #[derive(Default)] @@ -146,8 +148,9 @@ pub enum Regex { Group(Vec), // (at_least, at_most, _) Many(usize, usize, Box), - // (delimiter, x) - delimit x with `delimiter` on either side (used for raw strings) + // (delimiter, x) - parse a pattern, then refer to the substring later in x with `~` Delim(Box, Box), + Rewind(Box), } struct State<'a> { @@ -163,7 +166,6 @@ impl State<'_> { fn prev(&self) -> Option { self.s[..self.pos].last().copied() - // self.s.get(self.pos.saturating_sub(1)).copied() } fn skip_if(&mut self, f: impl FnOnce(char) -> bool) -> Option<()> { @@ -242,6 +244,14 @@ impl State<'_> { self.delim = old_delim; res } + Regex::Rewind(r) => { + let old_pos = self.pos; + let res = self.go(r); + if res.is_some() { + self.pos = old_pos; + } + res + } } } } @@ -265,7 +275,7 @@ use chumsky::{ impl Regex { fn parser<'a>() -> impl Parser<'a, &'a str, Self, extra::Err>> { recursive(|regex| { - let metachars = r"{}[]()^$.|*+-?\/@~"; + let metachars = r"{}[]()^$.|*+-?\/@~%"; let char_ = choice(( none_of(metachars), // Escaped meta characters @@ -307,6 +317,7 @@ impl Regex { postfix(1, just('*'), |r, _, _| Self::Many(0, !0, Box::new(r))), postfix(1, just('+'), |r, _, _| Self::Many(1, !0, Box::new(r))), postfix(1, just('?'), |r, _, _| Self::Many(0, 1, Box::new(r))), + postfix(1, just('%'), |r, _, _| Self::Rewind(Box::new(r))), // Non-standard: `x@y` parses `x` and then `y`. `y` can use `~` to refer to the extra string that was // parsed by `x`. This supports nesting and is intended for context-sensitive patterns like Rust raw // strings. diff --git a/src/lang/mod.rs b/src/lang/mod.rs index 04c3717..226186e 100644 --- a/src/lang/mod.rs +++ b/src/lang/mod.rs @@ -102,8 +102,10 @@ impl Highlighter { ) .with( TokenKind::Operator, - r"[(&(mut)?)(\?)(\+=?)(\-=?)(\*=?)(\/=?)(%=?)(!=?)(==?)(&&?=?)(\|\|?=?)(<>?=?)(\.\.[\.=]?)\\\~\^:;,\@(=>?)]", + r"[(&(mut)?)(\?)(\+=?)(\-=?)(\*=?)(\/=?)(\%=?)(!=?)(==?)(&&?=?)(\|\|?=?)(<>?=?)(\.\.[\.=]?)\\\~\^:;,\@(=>?)]", ) + // Function/method call + .with(TokenKind::Function, r"(\.)?\b[a-z_][A-Za-z0-9_]*\b[\(<]%") // Fields and methods: a.foo .with(TokenKind::Property, r"\.[a-z_][A-Za-z0-9_]*") // Paths: std::foo::bar @@ -145,8 +147,10 @@ impl Highlighter { .with(TokenKind::String, r#"b?'[(\\[nrt\\0(x[0-7A-Za-z][0-7A-Za-z])])[^']]*'"#) .with( TokenKind::Operator, - r"[(&)(\?)(\+\+)(\-\-)(\+=?)(\-=?)(\*=?)(\/=?)(%=?)(!=?)(==?)(&&?=?)(\|\|?=?)(<>?=?)(\.\.[\.=]?)\\\~\^:;,\@(=>?)]", + r"[(&)(\?)(\+\+)(\-\-)(\+=?)(\-=?)(\*=?)(\/=?)(\%=?)(!=?)(==?)(&&?=?)(\|\|?=?)(<>?=?)(\.\.[\.=]?)\\\~\^:;,\@(=>?)]", ) + // Function/method call + .with(TokenKind::Function, r"(\.)?\b[a-z_][A-Za-z0-9_]*\b[\(<]%") // Fields and methods: a.foo .with(TokenKind::Property, r"\.[a-z_][A-Za-z0-9_]*") // Paths: std::foo::bar @@ -159,7 +163,7 @@ impl Highlighter { pub fn generic_clike(self) -> Self { self // Keywords - .with(TokenKind::Keyword, r"\b[(var)(enum)(let)(this)(fn)(struct)(class)(import)(if)(while)(for)(in)(loop)(else)(break)(continue)(const)(static)(type)(extern)(return)(async)(throw)(catch)(union)(auto)(namespace)(public)(private)(function)(func)]\b") + .with(TokenKind::Keyword, r"\b[(var)(enum)(let)(this)(fn)(struct)(class)(import)(if)(while)(for)(in)(loop)(else)(break)(continue)(const)(static)(type)(extern)(return)(async)(throw)(catch)(union)(auto)(namespace)(public)(private)(function)(func)(goto)]\b") // Primitives .with(TokenKind::Type, r"\b[(([(unsigned)(signed)][[:space:]])*u?int[0-9]*(_t)?)(float)(double)(bool)(char)(size_t)(void)]\b") .clike_comments() diff --git a/src/theme.rs b/src/theme.rs index 8f39af9..c2d8aca 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -63,6 +63,7 @@ pub struct Theme { pub hl_token_string: Color, pub hl_token_special: Color, pub hl_token_constant: Color, + pub hl_token_function: Color, } impl Default for Theme { @@ -101,6 +102,7 @@ impl Default for Theme { hl_token_string: Color::AnsiValue(179), hl_token_special: Color::AnsiValue(160), hl_token_constant: Color::AnsiValue(81), + hl_token_function: Color::AnsiValue(122), } } } @@ -123,6 +125,7 @@ impl Theme { TokenKind::String => self.hl_token_string, TokenKind::Special => self.hl_token_special, TokenKind::Constant => self.hl_token_constant, + TokenKind::Function => self.hl_token_function, } } } diff --git a/src/ui/root.rs b/src/ui/root.rs index 4f8d59a..465e25d 100644 --- a/src/ui/root.rs +++ b/src/ui/root.rs @@ -112,11 +112,15 @@ impl Element<()> for Root { } Action::Cancel => { let unsaved = state.buffers.values().filter(|b| b.unsaved).count(); - if unsaved == 0 { + if state.buffers.is_empty() { return Ok(Resp::end(None)); } else { self.tasks.push(Task::Confirm(Confirm { - label: Label(format!("Are you sure you wish to quit? (y/n). Note that {} files are unsaved!", unsaved)), + label: Label(if unsaved == 0 { + format!("Are you sure you wish to quit? (y/n). You have multiple documents open!") + } else { + format!("Are you sure you wish to quit? (y/n). Note that {} files are unsaved!", unsaved) + }), action: Action::Quit, })); }