Add case sensitive and use regex toggles for find, fixes #144

This commit is contained in:
Jeremy Soller 2024-03-25 10:18:12 -06:00
parent abfb5d845f
commit 59933e6a03
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
6 changed files with 133 additions and 33 deletions

1
Cargo.lock generated
View file

@ -1107,6 +1107,7 @@ dependencies = [
"notify",
"open",
"patch",
"regex",
"rust-embed",
"serde",
"smol_str",

View file

@ -16,9 +16,10 @@ grep = "0.3.1"
ignore = "0.4.21"
lexical-sort = "0.3.1"
log = "0.4.20"
notify = "6.1.1"
open = "5.0.2"
patch = "0.7.0"
notify = "6.1.1"
regex = "1.10"
serde = { version = "1", features = ["serde_derive"] }
tokio = { version = "1", features = ["process", "time"] }
# Extra syntax highlighting
@ -58,4 +59,4 @@ wgpu = ["libcosmic/wgpu", "cosmic-files/wgpu"]
[profile.release-with-debug]
inherits = "release"
debug = true
debug = true

View file

@ -55,6 +55,8 @@ find-next = Find next
replace-placeholder = Replace...
replace = Replace
replace-all = Replace all
case-sensitive = Case sensitive
use-regex = Use regex
# Menu

View file

@ -31,6 +31,8 @@ impl AppTheme {
pub struct Config {
pub app_theme: AppTheme,
pub auto_indent: bool,
pub find_case_sensitive: bool,
pub find_use_regex: bool,
pub font_name: String,
pub font_size: u16,
pub highlight_current_line: bool,
@ -47,6 +49,8 @@ impl Default for Config {
Self {
app_theme: AppTheme::System,
auto_indent: true,
find_case_sensitive: false,
find_use_regex: false,
font_name: "Fira Mono".to_string(),
font_size: 14,
highlight_current_line: true,
@ -61,6 +65,16 @@ impl Default for Config {
}
impl Config {
pub fn find_regex(&self, pattern: &str) -> Result<regex::Regex, regex::Error> {
let mut builder = if self.find_use_regex {
regex::RegexBuilder::new(pattern)
} else {
regex::RegexBuilder::new(&regex::escape(pattern))
};
builder.case_insensitive(!self.find_case_sensitive);
builder.build()
}
// Calculate metrics from font size
pub fn metrics(&self) -> Metrics {
let font_size = self.font_size.max(1) as f32;

View file

@ -317,12 +317,14 @@ pub enum Message {
DefaultFontSize(usize),
DialogMessage(DialogMessage),
Find(Option<bool>),
FindCaseSensitive(bool),
FindNext,
FindPrevious,
FindReplace,
FindReplaceAll,
FindReplaceValueChanged(String),
FindSearchValueChanged(String),
FindUseRegex(bool),
GitProjectStatus(Vec<(String, PathBuf, Vec<GitStatus>)>),
Key(Modifiers, keyboard::Key),
LaunchUrl(String),
@ -1556,10 +1558,27 @@ impl Application for App {
// Focus correct input
return self.update_focus();
}
Message::FindCaseSensitive(find_case_sensitive) => {
self.config.find_case_sensitive = find_case_sensitive;
return self.save_config();
}
Message::FindNext => {
if !self.find_search_value.is_empty() {
if let Some(Tab::Editor(tab)) = self.active_tab() {
tab.search(&self.find_search_value, true);
//TODO: do not compile find regex on every search?
match self.config.find_regex(&self.find_search_value) {
Ok(regex) => {
tab.search(&regex, true);
}
Err(err) => {
//TODO: put regex error in find box
log::warn!(
"failed to compile regex {:?}: {}",
self.find_search_value,
err
);
}
}
}
}
@ -1569,7 +1588,20 @@ impl Application for App {
Message::FindPrevious => {
if !self.find_search_value.is_empty() {
if let Some(Tab::Editor(tab)) = self.active_tab() {
tab.search(&self.find_search_value, false);
//TODO: do not compile find regex on every search?
match self.config.find_regex(&self.find_search_value) {
Ok(regex) => {
tab.search(&regex, false);
}
Err(err) => {
//TODO: put regex error in find box
log::warn!(
"failed to compile regex {:?}: {}",
self.find_search_value,
err
);
}
}
}
}
@ -1579,7 +1611,21 @@ impl Application for App {
Message::FindReplace => {
if !self.find_search_value.is_empty() {
if let Some(Tab::Editor(tab)) = self.active_tab() {
tab.replace(&self.find_search_value, &self.find_replace_value);
//TODO: do not compile find regex on every search?
match self.config.find_regex(&self.find_search_value) {
Ok(regex) => {
//TODO: support captures
tab.replace(&regex, &self.find_replace_value);
}
Err(err) => {
//TODO: put regex error in find box
log::warn!(
"failed to compile regex {:?}: {}",
self.find_search_value,
err
);
}
}
}
}
@ -1589,11 +1635,25 @@ impl Application for App {
Message::FindReplaceAll => {
if !self.find_search_value.is_empty() {
if let Some(Tab::Editor(tab)) = self.active_tab() {
{
let mut editor = tab.editor.lock().unwrap();
editor.set_cursor(cosmic_text::Cursor::new(0, 0));
//TODO: do not compile find regex on every search?
match self.config.find_regex(&self.find_search_value) {
Ok(regex) => {
//TODO: support captures
{
let mut editor = tab.editor.lock().unwrap();
editor.set_cursor(cosmic_text::Cursor::new(0, 0));
}
while tab.replace(&regex, &self.find_replace_value) {}
}
Err(err) => {
//TODO: put regex error in find box
log::warn!(
"failed to compile regex {:?}: {}",
self.find_search_value,
err
);
}
}
while tab.replace(&self.find_search_value, &self.find_replace_value) {}
}
}
@ -1606,6 +1666,10 @@ impl Application for App {
Message::FindSearchValueChanged(value) => {
self.find_search_value = value;
}
Message::FindUseRegex(find_use_regex) => {
self.config.find_use_regex = find_use_regex;
return self.save_config();
}
Message::GitProjectStatus(project_status) => {
self.git_project_status = Some(project_status);
}
@ -2480,7 +2544,7 @@ impl Application for App {
.padding(space_xxs)
.spacing(space_xxs);
let mut column = widget::column::with_capacity(2).push(find_widget);
let mut column = widget::column::with_capacity(3).push(find_widget);
if *replace {
let replace_input = widget::text_input::text_input(
fl!("replace-placeholder"),
@ -2524,6 +2588,26 @@ impl Application for App {
column = column.push(replace_widget);
}
column = column.push(
widget::row::with_children(vec![
widget::checkbox(
fl!("case-sensitive"),
self.config.find_case_sensitive,
Message::FindCaseSensitive,
)
.into(),
widget::checkbox(
fl!("use-regex"),
self.config.find_use_regex,
Message::FindUseRegex,
)
.into(),
])
.align_items(Alignment::Center)
.padding(space_xxs)
.spacing(space_xxs),
);
tab_column = tab_column
.push(widget::layer_container(column).layer(cosmic_theme::Layer::Primary));
}

View file

@ -7,6 +7,7 @@ use cosmic::{
use cosmic_files::mime_icon::{mime_for_path, mime_icon, FALLBACK_MIME_ICON};
use cosmic_text::{Attrs, Buffer, Edit, Shaping, SyntaxEditor, ViEditor, Wrap};
use notify::Watcher;
use regex::Regex;
use std::{
fs,
path::PathBuf,
@ -208,18 +209,17 @@ impl EditorTab {
}
}
pub fn replace(&self, value: &str, replace: &str) -> bool {
pub fn replace(&self, regex: &Regex, replace: &str) -> bool {
let mut editor = self.editor.lock().unwrap();
let mut cursor = editor.cursor();
let start_line = cursor.line;
while cursor.line < editor.with_buffer(|buffer| buffer.lines.len()) {
if let Some(index) = editor.with_buffer(|buffer| {
buffer.lines[cursor.line]
.text()
.match_indices(value)
.filter_map(|(i, _)| {
if cursor.line != start_line || i >= cursor.index {
Some(i)
if let Some((index, len)) = editor.with_buffer(|buffer| {
regex
.find_iter(buffer.lines[cursor.line].text())
.filter_map(|m| {
if cursor.line != start_line || m.start() >= cursor.index {
Some((m.start(), m.len()))
} else {
None
}
@ -228,7 +228,7 @@ impl EditorTab {
}) {
cursor.index = index;
let mut end = cursor;
end.index = index + value.len();
end.index = index + len;
editor.start_change();
editor.delete_range(cursor, end);
@ -245,19 +245,18 @@ impl EditorTab {
}
// Code adapted from cosmic-text ViEditor search
pub fn search(&self, value: &str, forwards: bool) -> bool {
pub fn search(&self, regex: &Regex, forwards: bool) -> bool {
let mut editor = self.editor.lock().unwrap();
let mut cursor = editor.cursor();
let start_line = cursor.line;
if forwards {
while cursor.line < editor.with_buffer(|buffer| buffer.lines.len()) {
if let Some(index) = editor.with_buffer(|buffer| {
buffer.lines[cursor.line]
.text()
.match_indices(value)
.filter_map(|(i, _)| {
if cursor.line != start_line || i > cursor.index {
Some(i)
regex
.find_iter(buffer.lines[cursor.line].text())
.filter_map(|m| {
if cursor.line != start_line || m.start() > cursor.index {
Some(m.start())
} else {
None
}
@ -277,17 +276,16 @@ impl EditorTab {
cursor.line -= 1;
if let Some(index) = editor.with_buffer(|buffer| {
buffer.lines[cursor.line]
.text()
.rmatch_indices(value)
.filter_map(|(i, _)| {
if cursor.line != start_line || i < cursor.index {
Some(i)
regex
.find_iter(buffer.lines[cursor.line].text())
.filter_map(|m| {
if cursor.line != start_line || m.start() < cursor.index {
Some(m.start())
} else {
None
}
})
.next()
.last()
}) {
cursor.index = index;
editor.set_cursor(cursor);