Add case sensitive and use regex toggles for find, fixes #144
This commit is contained in:
parent
abfb5d845f
commit
59933e6a03
6 changed files with 133 additions and 33 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1107,6 +1107,7 @@ dependencies = [
|
||||||
"notify",
|
"notify",
|
||||||
"open",
|
"open",
|
||||||
"patch",
|
"patch",
|
||||||
|
"regex",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"smol_str",
|
"smol_str",
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,10 @@ grep = "0.3.1"
|
||||||
ignore = "0.4.21"
|
ignore = "0.4.21"
|
||||||
lexical-sort = "0.3.1"
|
lexical-sort = "0.3.1"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
notify = "6.1.1"
|
||||||
open = "5.0.2"
|
open = "5.0.2"
|
||||||
patch = "0.7.0"
|
patch = "0.7.0"
|
||||||
notify = "6.1.1"
|
regex = "1.10"
|
||||||
serde = { version = "1", features = ["serde_derive"] }
|
serde = { version = "1", features = ["serde_derive"] }
|
||||||
tokio = { version = "1", features = ["process", "time"] }
|
tokio = { version = "1", features = ["process", "time"] }
|
||||||
# Extra syntax highlighting
|
# Extra syntax highlighting
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@ find-next = Find next
|
||||||
replace-placeholder = Replace...
|
replace-placeholder = Replace...
|
||||||
replace = Replace
|
replace = Replace
|
||||||
replace-all = Replace all
|
replace-all = Replace all
|
||||||
|
case-sensitive = Case sensitive
|
||||||
|
use-regex = Use regex
|
||||||
|
|
||||||
# Menu
|
# Menu
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ impl AppTheme {
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub app_theme: AppTheme,
|
pub app_theme: AppTheme,
|
||||||
pub auto_indent: bool,
|
pub auto_indent: bool,
|
||||||
|
pub find_case_sensitive: bool,
|
||||||
|
pub find_use_regex: bool,
|
||||||
pub font_name: String,
|
pub font_name: String,
|
||||||
pub font_size: u16,
|
pub font_size: u16,
|
||||||
pub highlight_current_line: bool,
|
pub highlight_current_line: bool,
|
||||||
|
|
@ -47,6 +49,8 @@ impl Default for Config {
|
||||||
Self {
|
Self {
|
||||||
app_theme: AppTheme::System,
|
app_theme: AppTheme::System,
|
||||||
auto_indent: true,
|
auto_indent: true,
|
||||||
|
find_case_sensitive: false,
|
||||||
|
find_use_regex: false,
|
||||||
font_name: "Fira Mono".to_string(),
|
font_name: "Fira Mono".to_string(),
|
||||||
font_size: 14,
|
font_size: 14,
|
||||||
highlight_current_line: true,
|
highlight_current_line: true,
|
||||||
|
|
@ -61,6 +65,16 @@ impl Default for Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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(®ex::escape(pattern))
|
||||||
|
};
|
||||||
|
builder.case_insensitive(!self.find_case_sensitive);
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate metrics from font size
|
// Calculate metrics from font size
|
||||||
pub fn metrics(&self) -> Metrics {
|
pub fn metrics(&self) -> Metrics {
|
||||||
let font_size = self.font_size.max(1) as f32;
|
let font_size = self.font_size.max(1) as f32;
|
||||||
|
|
|
||||||
100
src/main.rs
100
src/main.rs
|
|
@ -317,12 +317,14 @@ pub enum Message {
|
||||||
DefaultFontSize(usize),
|
DefaultFontSize(usize),
|
||||||
DialogMessage(DialogMessage),
|
DialogMessage(DialogMessage),
|
||||||
Find(Option<bool>),
|
Find(Option<bool>),
|
||||||
|
FindCaseSensitive(bool),
|
||||||
FindNext,
|
FindNext,
|
||||||
FindPrevious,
|
FindPrevious,
|
||||||
FindReplace,
|
FindReplace,
|
||||||
FindReplaceAll,
|
FindReplaceAll,
|
||||||
FindReplaceValueChanged(String),
|
FindReplaceValueChanged(String),
|
||||||
FindSearchValueChanged(String),
|
FindSearchValueChanged(String),
|
||||||
|
FindUseRegex(bool),
|
||||||
GitProjectStatus(Vec<(String, PathBuf, Vec<GitStatus>)>),
|
GitProjectStatus(Vec<(String, PathBuf, Vec<GitStatus>)>),
|
||||||
Key(Modifiers, keyboard::Key),
|
Key(Modifiers, keyboard::Key),
|
||||||
LaunchUrl(String),
|
LaunchUrl(String),
|
||||||
|
|
@ -1556,10 +1558,27 @@ impl Application for App {
|
||||||
// Focus correct input
|
// Focus correct input
|
||||||
return self.update_focus();
|
return self.update_focus();
|
||||||
}
|
}
|
||||||
|
Message::FindCaseSensitive(find_case_sensitive) => {
|
||||||
|
self.config.find_case_sensitive = find_case_sensitive;
|
||||||
|
return self.save_config();
|
||||||
|
}
|
||||||
Message::FindNext => {
|
Message::FindNext => {
|
||||||
if !self.find_search_value.is_empty() {
|
if !self.find_search_value.is_empty() {
|
||||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
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(®ex, 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 => {
|
Message::FindPrevious => {
|
||||||
if !self.find_search_value.is_empty() {
|
if !self.find_search_value.is_empty() {
|
||||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
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(®ex, 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 => {
|
Message::FindReplace => {
|
||||||
if !self.find_search_value.is_empty() {
|
if !self.find_search_value.is_empty() {
|
||||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
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(®ex, &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 => {
|
Message::FindReplaceAll => {
|
||||||
if !self.find_search_value.is_empty() {
|
if !self.find_search_value.is_empty() {
|
||||||
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
if let Some(Tab::Editor(tab)) = self.active_tab() {
|
||||||
{
|
//TODO: do not compile find regex on every search?
|
||||||
let mut editor = tab.editor.lock().unwrap();
|
match self.config.find_regex(&self.find_search_value) {
|
||||||
editor.set_cursor(cosmic_text::Cursor::new(0, 0));
|
Ok(regex) => {
|
||||||
|
//TODO: support captures
|
||||||
|
{
|
||||||
|
let mut editor = tab.editor.lock().unwrap();
|
||||||
|
editor.set_cursor(cosmic_text::Cursor::new(0, 0));
|
||||||
|
}
|
||||||
|
while tab.replace(®ex, &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) => {
|
Message::FindSearchValueChanged(value) => {
|
||||||
self.find_search_value = 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) => {
|
Message::GitProjectStatus(project_status) => {
|
||||||
self.git_project_status = Some(project_status);
|
self.git_project_status = Some(project_status);
|
||||||
}
|
}
|
||||||
|
|
@ -2480,7 +2544,7 @@ impl Application for App {
|
||||||
.padding(space_xxs)
|
.padding(space_xxs)
|
||||||
.spacing(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 {
|
if *replace {
|
||||||
let replace_input = widget::text_input::text_input(
|
let replace_input = widget::text_input::text_input(
|
||||||
fl!("replace-placeholder"),
|
fl!("replace-placeholder"),
|
||||||
|
|
@ -2524,6 +2588,26 @@ impl Application for App {
|
||||||
column = column.push(replace_widget);
|
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
|
tab_column = tab_column
|
||||||
.push(widget::layer_container(column).layer(cosmic_theme::Layer::Primary));
|
.push(widget::layer_container(column).layer(cosmic_theme::Layer::Primary));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
44
src/tab.rs
44
src/tab.rs
|
|
@ -7,6 +7,7 @@ use cosmic::{
|
||||||
use cosmic_files::mime_icon::{mime_for_path, mime_icon, FALLBACK_MIME_ICON};
|
use cosmic_files::mime_icon::{mime_for_path, mime_icon, FALLBACK_MIME_ICON};
|
||||||
use cosmic_text::{Attrs, Buffer, Edit, Shaping, SyntaxEditor, ViEditor, Wrap};
|
use cosmic_text::{Attrs, Buffer, Edit, Shaping, SyntaxEditor, ViEditor, Wrap};
|
||||||
use notify::Watcher;
|
use notify::Watcher;
|
||||||
|
use regex::Regex;
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs,
|
||||||
path::PathBuf,
|
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 editor = self.editor.lock().unwrap();
|
||||||
let mut cursor = editor.cursor();
|
let mut cursor = editor.cursor();
|
||||||
let start_line = cursor.line;
|
let start_line = cursor.line;
|
||||||
while cursor.line < editor.with_buffer(|buffer| buffer.lines.len()) {
|
while cursor.line < editor.with_buffer(|buffer| buffer.lines.len()) {
|
||||||
if let Some(index) = editor.with_buffer(|buffer| {
|
if let Some((index, len)) = editor.with_buffer(|buffer| {
|
||||||
buffer.lines[cursor.line]
|
regex
|
||||||
.text()
|
.find_iter(buffer.lines[cursor.line].text())
|
||||||
.match_indices(value)
|
.filter_map(|m| {
|
||||||
.filter_map(|(i, _)| {
|
if cursor.line != start_line || m.start() >= cursor.index {
|
||||||
if cursor.line != start_line || i >= cursor.index {
|
Some((m.start(), m.len()))
|
||||||
Some(i)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +228,7 @@ impl EditorTab {
|
||||||
}) {
|
}) {
|
||||||
cursor.index = index;
|
cursor.index = index;
|
||||||
let mut end = cursor;
|
let mut end = cursor;
|
||||||
end.index = index + value.len();
|
end.index = index + len;
|
||||||
|
|
||||||
editor.start_change();
|
editor.start_change();
|
||||||
editor.delete_range(cursor, end);
|
editor.delete_range(cursor, end);
|
||||||
|
|
@ -245,19 +245,18 @@ impl EditorTab {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code adapted from cosmic-text ViEditor search
|
// 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 editor = self.editor.lock().unwrap();
|
||||||
let mut cursor = editor.cursor();
|
let mut cursor = editor.cursor();
|
||||||
let start_line = cursor.line;
|
let start_line = cursor.line;
|
||||||
if forwards {
|
if forwards {
|
||||||
while cursor.line < editor.with_buffer(|buffer| buffer.lines.len()) {
|
while cursor.line < editor.with_buffer(|buffer| buffer.lines.len()) {
|
||||||
if let Some(index) = editor.with_buffer(|buffer| {
|
if let Some(index) = editor.with_buffer(|buffer| {
|
||||||
buffer.lines[cursor.line]
|
regex
|
||||||
.text()
|
.find_iter(buffer.lines[cursor.line].text())
|
||||||
.match_indices(value)
|
.filter_map(|m| {
|
||||||
.filter_map(|(i, _)| {
|
if cursor.line != start_line || m.start() > cursor.index {
|
||||||
if cursor.line != start_line || i > cursor.index {
|
Some(m.start())
|
||||||
Some(i)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -277,17 +276,16 @@ impl EditorTab {
|
||||||
cursor.line -= 1;
|
cursor.line -= 1;
|
||||||
|
|
||||||
if let Some(index) = editor.with_buffer(|buffer| {
|
if let Some(index) = editor.with_buffer(|buffer| {
|
||||||
buffer.lines[cursor.line]
|
regex
|
||||||
.text()
|
.find_iter(buffer.lines[cursor.line].text())
|
||||||
.rmatch_indices(value)
|
.filter_map(|m| {
|
||||||
.filter_map(|(i, _)| {
|
if cursor.line != start_line || m.start() < cursor.index {
|
||||||
if cursor.line != start_line || i < cursor.index {
|
Some(m.start())
|
||||||
Some(i)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.next()
|
.last()
|
||||||
}) {
|
}) {
|
||||||
cursor.index = index;
|
cursor.index = index;
|
||||||
editor.set_cursor(cursor);
|
editor.set_cursor(cursor);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue