mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-04-02 17:45:28 +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 tag::Tag;
|
||||||
use widget::Widget;
|
use widget::Widget;
|
||||||
|
|
||||||
use crate::app::browser::window::{
|
use super::{TabAction, WindowAction};
|
||||||
action::Position, tab::item::Action as TabAction, Action as WindowAction,
|
use crate::app::browser::window::action::Position;
|
||||||
};
|
|
||||||
use gemtext::line::{
|
use gemtext::line::{
|
||||||
code::{Inline, Multiline},
|
code::{Inline, Multiline},
|
||||||
header::{Header, Level},
|
header::{Header, Level},
|
||||||
|
|
@ -24,8 +23,8 @@ use gtk::{
|
||||||
gio::Cancellable,
|
gio::Cancellable,
|
||||||
glib::{TimeZone, Uri},
|
glib::{TimeZone, Uri},
|
||||||
prelude::{TextBufferExt, TextBufferExtManual, TextTagExt, TextViewExt, WidgetExt},
|
prelude::{TextBufferExt, TextBufferExtManual, TextTagExt, TextViewExt, WidgetExt},
|
||||||
EventControllerMotion, GestureClick, TextBuffer, TextSearchFlags, TextTag, TextWindowType,
|
EventControllerMotion, GestureClick, TextBuffer, TextTag, TextWindowType, UriLauncher, Window,
|
||||||
UriLauncher, Window, WrapMode,
|
WrapMode,
|
||||||
};
|
};
|
||||||
use std::{cell::Cell, collections::HashMap, rc::Rc};
|
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);
|
const LINK_COLOR_ONHOVER: (f32, f32, f32, f32) = (53.0, 132.0, 228.0, 228.0);
|
||||||
|
|
||||||
pub struct Reader {
|
pub struct Reader {
|
||||||
buffer: TextBuffer,
|
|
||||||
tag: Tag,
|
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub widget: Rc<Widget>,
|
pub widget: Rc<Widget>,
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +47,7 @@ impl Reader {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
gemtext: &str,
|
gemtext: &str,
|
||||||
base: &Uri,
|
base: &Uri,
|
||||||
actions: (Rc<WindowAction>, Rc<TabAction>),
|
(window_action, tab_action): (Rc<WindowAction>, Rc<TabAction>),
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
// Init default values
|
// Init default values
|
||||||
let mut title = None;
|
let mut title = None;
|
||||||
|
|
@ -319,10 +316,11 @@ impl Reader {
|
||||||
|
|
||||||
// Init widget
|
// Init widget
|
||||||
let widget = Rc::new(Widget::new(
|
let widget = Rc::new(Widget::new(
|
||||||
|
&window_action,
|
||||||
&buffer,
|
&buffer,
|
||||||
primary_button_controller.clone(),
|
&primary_button_controller,
|
||||||
middle_button_controller.clone(),
|
&middle_button_controller,
|
||||||
motion_controller.clone(),
|
&motion_controller,
|
||||||
));
|
));
|
||||||
|
|
||||||
// Init shared reference container for HashTable collected
|
// Init shared reference container for HashTable collected
|
||||||
|
|
@ -348,7 +346,7 @@ impl Reader {
|
||||||
return match uri.scheme().as_str() {
|
return match uri.scheme().as_str() {
|
||||||
"gemini" => {
|
"gemini" => {
|
||||||
// Open new page in browser
|
// 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
|
// Scheme not supported, delegate
|
||||||
_ => UriLauncher::new(&uri.to_str()).launch(
|
_ => UriLauncher::new(&uri.to_str()).launch(
|
||||||
|
|
@ -385,7 +383,7 @@ impl Reader {
|
||||||
return match uri.scheme().as_str() {
|
return match uri.scheme().as_str() {
|
||||||
"gemini" => {
|
"gemini" => {
|
||||||
// Open new page in browser
|
// Open new page in browser
|
||||||
actions.0.append.activate_stateful_once(
|
window_action.append.activate_stateful_once(
|
||||||
Position::After,
|
Position::After,
|
||||||
Some(uri.to_string()),
|
Some(uri.to_string()),
|
||||||
false,
|
false,
|
||||||
|
|
@ -461,33 +459,7 @@ impl Reader {
|
||||||
}); // @TODO may be expensive for CPU, add timeout?
|
}); // @TODO may be expensive for CPU, add timeout?
|
||||||
|
|
||||||
// Result
|
// Result
|
||||||
Ok(Self {
|
Ok(Self { title, widget })
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
mod found;
|
|
||||||
mod h1;
|
mod h1;
|
||||||
mod h2;
|
mod h2;
|
||||||
mod h3;
|
mod h3;
|
||||||
|
|
@ -6,7 +5,6 @@ mod list;
|
||||||
mod quote;
|
mod quote;
|
||||||
mod title;
|
mod title;
|
||||||
|
|
||||||
use found::Found;
|
|
||||||
use h1::H1;
|
use h1::H1;
|
||||||
use h2::H2;
|
use h2::H2;
|
||||||
use h3::H3;
|
use h3::H3;
|
||||||
|
|
@ -25,7 +23,6 @@ pub struct Tag {
|
||||||
pub list: List,
|
pub list: List,
|
||||||
pub quote: Quote,
|
pub quote: Quote,
|
||||||
pub title: Title,
|
pub title: Title,
|
||||||
pub found: Found,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tag {
|
impl Tag {
|
||||||
|
|
@ -38,7 +35,6 @@ impl Tag {
|
||||||
let list = List::new();
|
let list = List::new();
|
||||||
let quote = Quote::new();
|
let quote = Quote::new();
|
||||||
let title = Title::new();
|
let title = Title::new();
|
||||||
let found = Found::new();
|
|
||||||
|
|
||||||
// Init tag table
|
// Init tag table
|
||||||
let text_tag_table = TextTagTable::new();
|
let text_tag_table = TextTagTable::new();
|
||||||
|
|
@ -49,7 +45,6 @@ impl Tag {
|
||||||
text_tag_table.add(&title.text_tag);
|
text_tag_table.add(&title.text_tag);
|
||||||
text_tag_table.add(&list.text_tag);
|
text_tag_table.add(&list.text_tag);
|
||||||
text_tag_table.add("e.text_tag);
|
text_tag_table.add("e.text_tag);
|
||||||
text_tag_table.add(&found.text_tag);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
text_tag_table,
|
text_tag_table,
|
||||||
|
|
@ -60,7 +55,6 @@ impl Tag {
|
||||||
list,
|
list,
|
||||||
quote,
|
quote,
|
||||||
title,
|
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;
|
mod find;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use find::Find;
|
use find::Find;
|
||||||
|
|
||||||
|
use super::WindowAction;
|
||||||
use gtk::{
|
use gtk::{
|
||||||
prelude::{TextViewExt, WidgetExt},
|
prelude::{ButtonExt, TextViewExt, WidgetExt},
|
||||||
EventControllerMotion, GestureClick, TextBuffer, TextView, TextWindowType, WrapMode,
|
EventControllerMotion, GestureClick, TextBuffer, TextView, TextWindowType, WrapMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MARGIN: i32 = 8;
|
const MARGIN: i32 = 8;
|
||||||
|
|
||||||
pub struct Widget {
|
pub struct Widget {
|
||||||
find: Find,
|
|
||||||
pub text_view: TextView,
|
pub text_view: TextView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,13 +20,14 @@ impl Widget {
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
action: &WindowAction,
|
||||||
buffer: &TextBuffer,
|
buffer: &TextBuffer,
|
||||||
primary_button_controller: GestureClick,
|
primary_button_controller: &GestureClick,
|
||||||
middle_button_controller: GestureClick,
|
middle_button_controller: &GestureClick,
|
||||||
motion_controller: EventControllerMotion,
|
motion_controller: &EventControllerMotion,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Init components
|
// Init components
|
||||||
let find = Find::new();
|
let find = Rc::new(Find::new(buffer));
|
||||||
|
|
||||||
// Init main widget
|
// Init main widget
|
||||||
let text_view = TextView::builder()
|
let text_view = TextView::builder()
|
||||||
|
|
@ -40,24 +42,26 @@ impl Widget {
|
||||||
.wrap_mode(WrapMode::Word)
|
.wrap_mode(WrapMode::Word)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
text_view.add_controller(primary_button_controller);
|
text_view.add_controller(primary_button_controller.clone());
|
||||||
text_view.add_controller(middle_button_controller);
|
text_view.add_controller(middle_button_controller.clone());
|
||||||
text_view.add_controller(motion_controller);
|
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
|
// Done
|
||||||
Self { find, text_view }
|
Self { 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
use gtk::{
|
use gtk::{
|
||||||
gdk::Cursor,
|
gdk::{Cursor, RGBA},
|
||||||
prelude::{BoxExt, EditableExt, EntryExt},
|
prelude::{BoxExt, ButtonExt, EditableExt, EntryExt, TextBufferExt},
|
||||||
Box, Button, Entry, EntryIconPosition, Orientation,
|
Box, Button, Entry, EntryIconPosition, Orientation, TextBuffer, TextSearchFlags, TextTag,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MARGIN: i32 = 6;
|
const MARGIN: i32 = 6;
|
||||||
|
|
||||||
pub struct Find {
|
pub struct Find {
|
||||||
|
pub close: Button,
|
||||||
|
pub entry: Entry,
|
||||||
pub g_box: Box,
|
pub g_box: Box,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Find {
|
impl Find {
|
||||||
// Construct
|
// Construct
|
||||||
pub fn new() -> Self {
|
pub fn new(text_buffer: &TextBuffer) -> Self {
|
||||||
// Init components
|
// Init components
|
||||||
let close = Button::builder()
|
let close = Button::builder()
|
||||||
.cursor(&Cursor::from_name("default", None).unwrap())
|
.cursor(&Cursor::from_name("default", None).unwrap())
|
||||||
|
|
@ -34,6 +36,11 @@ impl Find {
|
||||||
.primary_icon_name("system-search-symbolic")
|
.primary_icon_name("system-search-symbolic")
|
||||||
.build();
|
.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
|
// Init main container
|
||||||
let g_box = Box::builder().orientation(Orientation::Horizontal).build();
|
let g_box = Box::builder().orientation(Orientation::Horizontal).build();
|
||||||
|
|
||||||
|
|
@ -41,14 +48,44 @@ impl Find {
|
||||||
g_box.append(&close);
|
g_box.append(&close);
|
||||||
|
|
||||||
// Connect events
|
// Connect events
|
||||||
entry.connect_activate(|_| {}); // @TODO
|
close.connect_clicked({
|
||||||
|
let entry = entry.clone();
|
||||||
|
move |_| entry.delete_text(0, -1)
|
||||||
|
});
|
||||||
|
|
||||||
entry.connect_changed(move |this| {
|
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() {
|
if this.text().is_empty() {
|
||||||
this.set_secondary_icon_name(None);
|
this.set_secondary_icon_name(None);
|
||||||
} else {
|
} else {
|
||||||
this.set_secondary_icon_name(Some("edit-clear-symbolic"));
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
entry.connect_icon_release(move |this, position| match position {
|
entry.connect_icon_release(move |this, position| match position {
|
||||||
|
|
@ -57,6 +94,10 @@ impl Find {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
Self { g_box }
|
Self {
|
||||||
|
close,
|
||||||
|
entry,
|
||||||
|
g_box,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue