mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-03-31 16:45:27 +00:00
implement search match highlight
This commit is contained in:
parent
db44fff212
commit
1b2aeae665
5 changed files with 91 additions and 99 deletions
|
|
@ -9,9 +9,8 @@ use syntax::Syntax;
|
|||
use tag::Tag;
|
||||
use widget::Widget;
|
||||
|
||||
use crate::app::browser::window::{
|
||||
action::Position, tab::item::Action as TabAction, Action as WindowAction,
|
||||
};
|
||||
use super::{TabAction, WindowAction};
|
||||
use crate::app::browser::window::action::Position;
|
||||
use gemtext::line::{
|
||||
code::{Inline, Multiline},
|
||||
header::{Header, Level},
|
||||
|
|
@ -24,8 +23,8 @@ use gtk::{
|
|||
gio::Cancellable,
|
||||
glib::{TimeZone, Uri},
|
||||
prelude::{TextBufferExt, TextBufferExtManual, TextTagExt, TextViewExt, WidgetExt},
|
||||
EventControllerMotion, GestureClick, TextBuffer, TextSearchFlags, TextTag, TextWindowType,
|
||||
UriLauncher, Window, WrapMode,
|
||||
EventControllerMotion, GestureClick, TextBuffer, TextTag, TextWindowType, UriLauncher, Window,
|
||||
WrapMode,
|
||||
};
|
||||
use std::{cell::Cell, collections::HashMap, rc::Rc};
|
||||
|
||||
|
|
@ -39,8 +38,6 @@ const LINK_COLOR_DEFAULT: (f32, f32, f32, f32) = (53.0, 132.0, 228.0, 255.0);
|
|||
const LINK_COLOR_ONHOVER: (f32, f32, f32, f32) = (53.0, 132.0, 228.0, 228.0);
|
||||
|
||||
pub struct Reader {
|
||||
buffer: TextBuffer,
|
||||
tag: Tag,
|
||||
pub title: Option<String>,
|
||||
pub widget: Rc<Widget>,
|
||||
}
|
||||
|
|
@ -50,7 +47,7 @@ impl Reader {
|
|||
pub fn new(
|
||||
gemtext: &str,
|
||||
base: &Uri,
|
||||
actions: (Rc<WindowAction>, Rc<TabAction>),
|
||||
(window_action, tab_action): (Rc<WindowAction>, Rc<TabAction>),
|
||||
) -> Result<Self, Error> {
|
||||
// Init default values
|
||||
let mut title = None;
|
||||
|
|
@ -319,10 +316,11 @@ impl Reader {
|
|||
|
||||
// Init widget
|
||||
let widget = Rc::new(Widget::new(
|
||||
&window_action,
|
||||
&buffer,
|
||||
primary_button_controller.clone(),
|
||||
middle_button_controller.clone(),
|
||||
motion_controller.clone(),
|
||||
&primary_button_controller,
|
||||
&middle_button_controller,
|
||||
&motion_controller,
|
||||
));
|
||||
|
||||
// Init shared reference container for HashTable collected
|
||||
|
|
@ -348,7 +346,7 @@ impl Reader {
|
|||
return match uri.scheme().as_str() {
|
||||
"gemini" => {
|
||||
// Open new page in browser
|
||||
actions.1.load.activate(Some(&uri.to_str()), true);
|
||||
tab_action.load.activate(Some(&uri.to_str()), true);
|
||||
}
|
||||
// Scheme not supported, delegate
|
||||
_ => UriLauncher::new(&uri.to_str()).launch(
|
||||
|
|
@ -385,7 +383,7 @@ impl Reader {
|
|||
return match uri.scheme().as_str() {
|
||||
"gemini" => {
|
||||
// Open new page in browser
|
||||
actions.0.append.activate_stateful_once(
|
||||
window_action.append.activate_stateful_once(
|
||||
Position::After,
|
||||
Some(uri.to_string()),
|
||||
false,
|
||||
|
|
@ -461,33 +459,7 @@ impl Reader {
|
|||
}); // @TODO may be expensive for CPU, add timeout?
|
||||
|
||||
// Result
|
||||
Ok(Self {
|
||||
buffer,
|
||||
tag,
|
||||
title,
|
||||
widget,
|
||||
})
|
||||
}
|
||||
|
||||
// Actions
|
||||
|
||||
pub fn find(&self) {
|
||||
self.buffer.remove_tag(
|
||||
&self.tag.found.text_tag,
|
||||
&self.buffer.start_iter(),
|
||||
&self.buffer.end_iter(),
|
||||
);
|
||||
|
||||
let mut next = self.buffer.start_iter();
|
||||
while let Some((start, end)) = next.forward_search(
|
||||
"Gemini",
|
||||
TextSearchFlags::CASE_INSENSITIVE, // @TODO
|
||||
None, // unlimited
|
||||
) {
|
||||
self.buffer
|
||||
.apply_tag(&self.tag.found.text_tag, &start, &end);
|
||||
next = end;
|
||||
}
|
||||
Ok(Self { title, widget })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
mod found;
|
||||
mod h1;
|
||||
mod h2;
|
||||
mod h3;
|
||||
|
|
@ -6,7 +5,6 @@ mod list;
|
|||
mod quote;
|
||||
mod title;
|
||||
|
||||
use found::Found;
|
||||
use h1::H1;
|
||||
use h2::H2;
|
||||
use h3::H3;
|
||||
|
|
@ -25,7 +23,6 @@ pub struct Tag {
|
|||
pub list: List,
|
||||
pub quote: Quote,
|
||||
pub title: Title,
|
||||
pub found: Found,
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
|
|
@ -38,7 +35,6 @@ impl Tag {
|
|||
let list = List::new();
|
||||
let quote = Quote::new();
|
||||
let title = Title::new();
|
||||
let found = Found::new();
|
||||
|
||||
// Init tag table
|
||||
let text_tag_table = TextTagTable::new();
|
||||
|
|
@ -49,7 +45,6 @@ impl Tag {
|
|||
text_tag_table.add(&title.text_tag);
|
||||
text_tag_table.add(&list.text_tag);
|
||||
text_tag_table.add("e.text_tag);
|
||||
text_tag_table.add(&found.text_tag);
|
||||
|
||||
Self {
|
||||
text_tag_table,
|
||||
|
|
@ -60,7 +55,6 @@ impl Tag {
|
|||
list,
|
||||
quote,
|
||||
title,
|
||||
found,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
use gtk::{gdk::RGBA, TextTag, WrapMode};
|
||||
|
||||
pub struct Found {
|
||||
pub text_tag: TextTag,
|
||||
}
|
||||
|
||||
impl Found {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self`
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
text_tag: TextTag::builder()
|
||||
.background_rgba(&RGBA::new(0.502, 0.502, 0.502, 0.5)) // @TODO
|
||||
.wrap_mode(WrapMode::Word)
|
||||
.build(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
mod find;
|
||||
use std::rc::Rc;
|
||||
|
||||
use find::Find;
|
||||
|
||||
use super::WindowAction;
|
||||
use gtk::{
|
||||
prelude::{TextViewExt, WidgetExt},
|
||||
prelude::{ButtonExt, TextViewExt, WidgetExt},
|
||||
EventControllerMotion, GestureClick, TextBuffer, TextView, TextWindowType, WrapMode,
|
||||
};
|
||||
|
||||
const MARGIN: i32 = 8;
|
||||
|
||||
pub struct Widget {
|
||||
find: Find,
|
||||
pub text_view: TextView,
|
||||
}
|
||||
|
||||
|
|
@ -19,13 +20,14 @@ impl Widget {
|
|||
|
||||
/// Create new `Self`
|
||||
pub fn new(
|
||||
action: &WindowAction,
|
||||
buffer: &TextBuffer,
|
||||
primary_button_controller: GestureClick,
|
||||
middle_button_controller: GestureClick,
|
||||
motion_controller: EventControllerMotion,
|
||||
primary_button_controller: &GestureClick,
|
||||
middle_button_controller: &GestureClick,
|
||||
motion_controller: &EventControllerMotion,
|
||||
) -> Self {
|
||||
// Init components
|
||||
let find = Find::new();
|
||||
let find = Rc::new(Find::new(buffer));
|
||||
|
||||
// Init main widget
|
||||
let text_view = TextView::builder()
|
||||
|
|
@ -40,24 +42,26 @@ impl Widget {
|
|||
.wrap_mode(WrapMode::Word)
|
||||
.build();
|
||||
|
||||
text_view.add_controller(primary_button_controller);
|
||||
text_view.add_controller(middle_button_controller);
|
||||
text_view.add_controller(motion_controller);
|
||||
text_view.add_controller(primary_button_controller.clone());
|
||||
text_view.add_controller(middle_button_controller.clone());
|
||||
text_view.add_controller(motion_controller.clone());
|
||||
|
||||
// Connect events
|
||||
action.find.connect_activate({
|
||||
let find = find.clone();
|
||||
let text_view = text_view.clone();
|
||||
move |_| {
|
||||
text_view.set_gutter(TextWindowType::Bottom, Some(&find.g_box));
|
||||
find.entry.grab_focus();
|
||||
}
|
||||
});
|
||||
|
||||
find.close.connect_clicked({
|
||||
let text_view = text_view.clone();
|
||||
move |_| text_view.set_gutter(TextWindowType::Bottom, gtk::Widget::NONE)
|
||||
});
|
||||
|
||||
// Done
|
||||
Self { find, text_view }
|
||||
}
|
||||
|
||||
// Actions
|
||||
|
||||
pub fn find(&self, is_visible: bool) {
|
||||
if is_visible {
|
||||
self.text_view
|
||||
.set_gutter(TextWindowType::Bottom, Some(&self.find.g_box));
|
||||
self.find.g_box.grab_focus();
|
||||
} else {
|
||||
self.text_view
|
||||
.set_gutter(TextWindowType::Bottom, gtk::Widget::NONE);
|
||||
}
|
||||
Self { text_view }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
use gtk::{
|
||||
gdk::Cursor,
|
||||
prelude::{BoxExt, EditableExt, EntryExt},
|
||||
Box, Button, Entry, EntryIconPosition, Orientation,
|
||||
gdk::{Cursor, RGBA},
|
||||
prelude::{BoxExt, ButtonExt, EditableExt, EntryExt, TextBufferExt},
|
||||
Box, Button, Entry, EntryIconPosition, Orientation, TextBuffer, TextSearchFlags, TextTag,
|
||||
};
|
||||
|
||||
const MARGIN: i32 = 6;
|
||||
|
||||
pub struct Find {
|
||||
pub close: Button,
|
||||
pub entry: Entry,
|
||||
pub g_box: Box,
|
||||
}
|
||||
|
||||
impl Find {
|
||||
// Construct
|
||||
pub fn new() -> Self {
|
||||
pub fn new(text_buffer: &TextBuffer) -> Self {
|
||||
// Init components
|
||||
let close = Button::builder()
|
||||
.cursor(&Cursor::from_name("default", None).unwrap())
|
||||
|
|
@ -34,6 +36,11 @@ impl Find {
|
|||
.primary_icon_name("system-search-symbolic")
|
||||
.build();
|
||||
|
||||
let text_tag = TextTag::builder()
|
||||
.background_rgba(&RGBA::new(0.502, 0.502, 0.502, 0.5)) // @TODO
|
||||
.build();
|
||||
text_buffer.tag_table().add(&text_tag);
|
||||
|
||||
// Init main container
|
||||
let g_box = Box::builder().orientation(Orientation::Horizontal).build();
|
||||
|
||||
|
|
@ -41,13 +48,43 @@ impl Find {
|
|||
g_box.append(&close);
|
||||
|
||||
// Connect events
|
||||
entry.connect_activate(|_| {}); // @TODO
|
||||
close.connect_clicked({
|
||||
let entry = entry.clone();
|
||||
move |_| entry.delete_text(0, -1)
|
||||
});
|
||||
|
||||
entry.connect_changed(move |this| {
|
||||
if this.text().is_empty() {
|
||||
this.set_secondary_icon_name(None);
|
||||
} else {
|
||||
this.set_secondary_icon_name(Some("edit-clear-symbolic"));
|
||||
entry.connect_changed({
|
||||
let entry = entry.clone();
|
||||
let text_buffer = text_buffer.clone();
|
||||
let text_tag = text_tag.clone();
|
||||
move |this| {
|
||||
// Toggle clear action
|
||||
if this.text().is_empty() {
|
||||
this.set_secondary_icon_name(None);
|
||||
} else {
|
||||
this.set_secondary_icon_name(Some("edit-clear-symbolic"));
|
||||
}
|
||||
|
||||
// Cleanup previous search results
|
||||
text_buffer.remove_tag(
|
||||
&text_tag,
|
||||
&text_buffer.start_iter(),
|
||||
&text_buffer.end_iter(),
|
||||
);
|
||||
|
||||
// Get subject once
|
||||
let query = entry.text();
|
||||
|
||||
// Begin search
|
||||
let mut next = text_buffer.start_iter();
|
||||
while let Some((start, end)) = next.forward_search(
|
||||
&query,
|
||||
TextSearchFlags::CASE_INSENSITIVE, // @TODO
|
||||
None, // unlimited
|
||||
) {
|
||||
text_buffer.apply_tag(&text_tag, &start, &end);
|
||||
next = end;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -57,6 +94,10 @@ impl Find {
|
|||
});
|
||||
|
||||
// Done
|
||||
Self { g_box }
|
||||
Self {
|
||||
close,
|
||||
entry,
|
||||
g_box,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue