mirror of
https://gitlab.com/gabmus/envision.git
synced 2025-08-11 02:28:43 +00:00
feat: better searching in debug view with gtk sourceview
This commit is contained in:
parent
512a0d6414
commit
c95ab3d10f
4 changed files with 175 additions and 45 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -1541,6 +1541,7 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sourceview5",
|
||||||
"tracker",
|
"tracker",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
@ -1701,6 +1702,42 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sourceview5"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee960607b1f7fda934dce68e76e925989ebe186ac04d6ab5ea9ce93e13835c03"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"gdk-pixbuf",
|
||||||
|
"gdk4",
|
||||||
|
"gio",
|
||||||
|
"glib",
|
||||||
|
"gtk4",
|
||||||
|
"libc",
|
||||||
|
"pango",
|
||||||
|
"sourceview5-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sourceview5-sys"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a23462cd3d696199b56317d35e69b240d655b8c70c12bd8f443b672313776c"
|
||||||
|
dependencies = [
|
||||||
|
"gdk-pixbuf-sys",
|
||||||
|
"gdk4-sys",
|
||||||
|
"gio-sys",
|
||||||
|
"glib-sys",
|
||||||
|
"gobject-sys",
|
||||||
|
"gtk4-sys",
|
||||||
|
"libc",
|
||||||
|
"pango-sys",
|
||||||
|
"system-deps",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
|
|
|
@ -29,5 +29,8 @@ serde = { version = "1.0.163", features = [
|
||||||
"derive"
|
"derive"
|
||||||
] }
|
] }
|
||||||
serde_json = "1.0.96"
|
serde_json = "1.0.96"
|
||||||
|
sourceview5 = { version = "0.6.1", features = [
|
||||||
|
"v5_6"
|
||||||
|
] }
|
||||||
tracker = "0.2.1"
|
tracker = "0.2.1"
|
||||||
uuid = { version = "1.3.4", features = ["v4", "fast-rng"] }
|
uuid = { version = "1.3.4", features = ["v4", "fast-rng"] }
|
||||||
|
|
|
@ -68,8 +68,6 @@ pub struct App {
|
||||||
#[tracker::do_not_track]
|
#[tracker::do_not_track]
|
||||||
xrservice_runner: Option<Runner>,
|
xrservice_runner: Option<Runner>,
|
||||||
#[tracker::do_not_track]
|
#[tracker::do_not_track]
|
||||||
xrservice_log: Vec<String>,
|
|
||||||
#[tracker::do_not_track]
|
|
||||||
build_pipeline: Option<RunnerPipeline>,
|
build_pipeline: Option<RunnerPipeline>,
|
||||||
#[tracker::do_not_track]
|
#[tracker::do_not_track]
|
||||||
profiles: Vec<Profile>,
|
profiles: Vec<Profile>,
|
||||||
|
@ -104,10 +102,9 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_xrservice(&mut self) {
|
pub fn start_xrservice(&mut self) {
|
||||||
self.xrservice_log.clear();
|
|
||||||
self.debug_view
|
self.debug_view
|
||||||
.sender()
|
.sender()
|
||||||
.emit(DebugViewMsg::LogUpdated(vec![]));
|
.emit(DebugViewMsg::ClearLog);
|
||||||
let mut runner = Runner::xrservice_runner_from_profile(self.get_selected_profile().clone());
|
let mut runner = Runner::xrservice_runner_from_profile(self.get_selected_profile().clone());
|
||||||
match runner.try_start() {
|
match runner.try_start() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
@ -192,10 +189,9 @@ impl SimpleComponent for App {
|
||||||
Some(runner) => {
|
Some(runner) => {
|
||||||
let n_rows = runner.consume_rows();
|
let n_rows = runner.consume_rows();
|
||||||
if !n_rows.is_empty() {
|
if !n_rows.is_empty() {
|
||||||
self.xrservice_log.extend(n_rows);
|
|
||||||
self.debug_view
|
self.debug_view
|
||||||
.sender()
|
.sender()
|
||||||
.emit(DebugViewMsg::LogUpdated(self.xrservice_log.clone()));
|
.emit(DebugViewMsg::LogUpdated(n_rows));
|
||||||
}
|
}
|
||||||
match runner.status() {
|
match runner.status() {
|
||||||
RunnerStatus::Running => {}
|
RunnerStatus::Running => {}
|
||||||
|
@ -504,7 +500,6 @@ impl SimpleComponent for App {
|
||||||
tracker: 0,
|
tracker: 0,
|
||||||
profiles,
|
profiles,
|
||||||
xrservice_runner: None,
|
xrservice_runner: None,
|
||||||
xrservice_log: vec![],
|
|
||||||
build_pipeline: None,
|
build_pipeline: None,
|
||||||
};
|
};
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
|
|
@ -2,6 +2,7 @@ use expect_dialog::ExpectDialog;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use relm4::prelude::*;
|
use relm4::prelude::*;
|
||||||
use relm4::{ComponentSender, SimpleComponent};
|
use relm4::{ComponentSender, SimpleComponent};
|
||||||
|
use sourceview5::prelude::*;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::slice::Iter;
|
use std::slice::Iter;
|
||||||
|
|
||||||
|
@ -50,29 +51,44 @@ impl Display for LogLevel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SearchDirection {
|
||||||
|
Forward,
|
||||||
|
Backward,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DebugViewMsg {
|
pub enum DebugViewMsg {
|
||||||
LogUpdated(Vec<String>),
|
LogUpdated(Vec<String>),
|
||||||
|
ClearLog,
|
||||||
EnableDebugViewChanged(bool),
|
EnableDebugViewChanged(bool),
|
||||||
FilterLog,
|
FilterLog(SearchDirection),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracker::track]
|
#[tracker::track]
|
||||||
pub struct DebugView {
|
pub struct DebugView {
|
||||||
#[tracker::do_not_track]
|
#[tracker::do_not_track]
|
||||||
pub log: Vec<String>,
|
log: Vec<String>,
|
||||||
#[tracker::do_not_track]
|
#[tracker::do_not_track]
|
||||||
pub textbuf: gtk::TextBuffer,
|
textbuf: sourceview5::Buffer,
|
||||||
#[tracker::do_not_track]
|
#[tracker::do_not_track]
|
||||||
pub textview: Option<gtk::TextView>,
|
textview: Option<sourceview5::View>,
|
||||||
#[tracker::do_not_track]
|
#[tracker::do_not_track]
|
||||||
pub searchbar: Option<gtk::SearchBar>,
|
searchbar: Option<gtk::SearchBar>,
|
||||||
#[tracker::do_not_track]
|
#[tracker::do_not_track]
|
||||||
pub search_entry: Option<gtk::SearchEntry>,
|
search_entry: Option<gtk::SearchEntry>,
|
||||||
#[tracker::do_not_track]
|
#[tracker::do_not_track]
|
||||||
pub dropdown: Option<gtk::DropDown>,
|
dropdown: Option<gtk::DropDown>,
|
||||||
|
#[tracker::do_not_track]
|
||||||
|
scrolledwin: Option<gtk::ScrolledWindow>,
|
||||||
|
#[tracker::do_not_track]
|
||||||
|
search_ctx: sourceview5::SearchContext,
|
||||||
|
#[tracker::do_not_track]
|
||||||
|
search_settings: sourceview5::SearchSettings,
|
||||||
|
#[tracker::do_not_track]
|
||||||
|
search_mark: Option<gtk::TextMark>,
|
||||||
|
|
||||||
pub enable_debug_view: bool,
|
enable_debug_view: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DebugViewInit {
|
pub struct DebugViewInit {
|
||||||
|
@ -122,24 +138,48 @@ impl SimpleComponent for DebugView {
|
||||||
#[chain(flags(gtk::glib::BindingFlags::BIDIRECTIONAL).build())]
|
#[chain(flags(gtk::glib::BindingFlags::BIDIRECTIONAL).build())]
|
||||||
bind_property: ("search-mode-enabled", &search_toggle, "active"),
|
bind_property: ("search-mode-enabled", &search_toggle, "active"),
|
||||||
#[wrap(Some)]
|
#[wrap(Some)]
|
||||||
set_child: search_entry = >k::SearchEntry {
|
set_child: searchbox = >k::Box {
|
||||||
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
add_css_class: "linked",
|
||||||
set_hexpand: true,
|
set_hexpand: true,
|
||||||
connect_changed[sender] => move |_| {
|
#[name(search_entry)]
|
||||||
sender.input(Self::Input::FilterLog);
|
gtk::SearchEntry {
|
||||||
}
|
set_hexpand: true,
|
||||||
|
connect_changed[sender] => move |_| {
|
||||||
|
sender.input(Self::Input::FilterLog(SearchDirection::Forward));
|
||||||
|
},
|
||||||
|
connect_activate[sender] => move |_| {
|
||||||
|
sender.input(Self::Input::FilterLog(SearchDirection::Forward));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
gtk::Button {
|
||||||
|
set_icon_name: "go-up-symbolic",
|
||||||
|
set_tooltip_text: Some("Previous Match"),
|
||||||
|
connect_clicked[sender] => move |_| {
|
||||||
|
sender.input(Self::Input::FilterLog(SearchDirection::Backward))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
gtk::Button {
|
||||||
|
set_icon_name: "go-down-symbolic",
|
||||||
|
set_tooltip_text: Some("Next Match"),
|
||||||
|
connect_clicked[sender] => move |_| {
|
||||||
|
sender.input(Self::Input::FilterLog(SearchDirection::Forward))
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
connect_entry: &search_entry,
|
connect_entry: &search_entry,
|
||||||
},
|
},
|
||||||
|
#[name(scrolledwin)]
|
||||||
gtk::ScrolledWindow {
|
gtk::ScrolledWindow {
|
||||||
set_hexpand: true,
|
set_hexpand: true,
|
||||||
set_vexpand: true,
|
set_vexpand: true,
|
||||||
#[name(textview)]
|
#[name(textview)]
|
||||||
gtk::TextView {
|
sourceview5::View {
|
||||||
set_hexpand: true,
|
set_hexpand: true,
|
||||||
set_vexpand: true,
|
set_vexpand: true,
|
||||||
set_editable: false,
|
set_editable: false,
|
||||||
set_monospace: true,
|
set_monospace: true,
|
||||||
set_buffer: Some(&model.textbuf)
|
set_buffer: Some(&model.textbuf),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,37 +189,71 @@ impl SimpleComponent for DebugView {
|
||||||
self.reset();
|
self.reset();
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
Self::Input::FilterLog => {
|
Self::Input::FilterLog(direction) => {
|
||||||
let searchbar = self.searchbar.as_ref().unwrap().clone();
|
let searchbar = self.searchbar.as_ref().unwrap().clone();
|
||||||
let search_entry = self.search_entry.as_ref().unwrap().clone();
|
let search_entry = self.search_entry.as_ref().unwrap().clone();
|
||||||
let search_text = search_entry.text().to_string().trim().to_lowercase();
|
let search_text = search_entry.text().to_string();
|
||||||
// TODO: add log level filtering
|
// TODO: add log level filtering
|
||||||
let log_level = LogLevel::iter()
|
let log_level = LogLevel::iter()
|
||||||
.as_slice()
|
.as_slice()
|
||||||
.get(self.dropdown.as_ref().unwrap().selected() as usize)
|
.get(self.dropdown.as_ref().unwrap().selected() as usize)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("log level: {}", log_level.to_string());
|
println!("log level: {}", log_level.to_string());
|
||||||
let log = match searchbar.is_search_mode() && !search_text.is_empty() {
|
if searchbar.is_search_mode() && !search_text.is_empty() {
|
||||||
true => self
|
self.search_settings
|
||||||
.log
|
.set_search_text(Some(search_text.as_str()));
|
||||||
.iter()
|
self.search_mark = Some(self.textbuf.get_insert());
|
||||||
.filter(|row| row.to_lowercase().contains(&search_text))
|
let mut iter = self
|
||||||
.map(|s| s.to_string())
|
.textbuf
|
||||||
.collect::<Vec<String>>()
|
.iter_at_mark(self.search_mark.as_ref().unwrap());
|
||||||
.concat(),
|
iter.forward_char();
|
||||||
false => self.log.concat(),
|
let search_res = match direction {
|
||||||
};
|
SearchDirection::Forward => self.search_ctx.forward(&iter),
|
||||||
self.textbuf.set_text(&log);
|
SearchDirection::Backward => self.search_ctx.backward(&iter),
|
||||||
let textbuf = self.textbuf.clone();
|
};
|
||||||
let textview = self.textview.as_ref().unwrap().clone();
|
match search_res {
|
||||||
gtk::glib::idle_add_local_once(move || {
|
None => {
|
||||||
let end_mark = textbuf.create_mark(None, &textbuf.end_iter(), false);
|
// TODO: mark search entry red
|
||||||
textview.scroll_mark_onscreen(&end_mark);
|
}
|
||||||
});
|
Some((start, end, _)) => {
|
||||||
|
self.textbuf.move_mark(
|
||||||
|
self.search_mark.as_ref().unwrap(),
|
||||||
|
match direction {
|
||||||
|
SearchDirection::Forward => &end,
|
||||||
|
SearchDirection::Backward => &start,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.textbuf.select_range(&start, &end);
|
||||||
|
self.textview
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.scroll_mark_onscreen(&self.textbuf.create_mark(None, &end, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.search_settings.set_search_text(None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Self::Input::LogUpdated(n_log) => {
|
Self::Input::LogUpdated(n_log) => {
|
||||||
self.log = n_log;
|
let is_at_bottom = {
|
||||||
sender.input(Self::Input::FilterLog);
|
let adj = self.scrolledwin.as_ref().unwrap().vadjustment();
|
||||||
|
(adj.upper() - adj.page_size() - adj.value()) <= 15.0
|
||||||
|
};
|
||||||
|
self.log.extend(n_log.clone());
|
||||||
|
self.textbuf
|
||||||
|
.insert(&mut self.textbuf.end_iter(), n_log.concat().as_str());
|
||||||
|
let textbuf = self.textbuf.clone();
|
||||||
|
let textview = self.textview.as_ref().unwrap().clone();
|
||||||
|
if is_at_bottom && !self.searchbar.as_ref().unwrap().is_search_mode() {
|
||||||
|
gtk::glib::idle_add_local_once(move || {
|
||||||
|
let end_mark = textbuf.create_mark(None, &textbuf.end_iter(), false);
|
||||||
|
textview.scroll_mark_onscreen(&end_mark);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Input::ClearLog => {
|
||||||
|
self.log = vec![];
|
||||||
|
self.textbuf.set_text("");
|
||||||
}
|
}
|
||||||
Self::Input::EnableDebugViewChanged(val) => self.set_enable_debug_view(val),
|
Self::Input::EnableDebugViewChanged(val) => self.set_enable_debug_view(val),
|
||||||
}
|
}
|
||||||
|
@ -190,15 +264,35 @@ impl SimpleComponent for DebugView {
|
||||||
root: &Self::Root,
|
root: &Self::Root,
|
||||||
sender: ComponentSender<Self>,
|
sender: ComponentSender<Self>,
|
||||||
) -> ComponentParts<Self> {
|
) -> ComponentParts<Self> {
|
||||||
|
let textbuf = sourceview5::Buffer::builder()
|
||||||
|
.highlight_syntax(false)
|
||||||
|
.style_scheme(
|
||||||
|
&sourceview5::StyleSchemeManager::new()
|
||||||
|
.scheme("Adwaita-dark")
|
||||||
|
.expect_dialog("Couldn't find Adwaita-dark style scheme for gtksourceview5"),
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
let search_settings = sourceview5::SearchSettings::builder()
|
||||||
|
.wrap_around(true)
|
||||||
|
.case_sensitive(false)
|
||||||
|
.build();
|
||||||
|
let search_ctx = sourceview5::SearchContext::builder()
|
||||||
|
.buffer(&textbuf)
|
||||||
|
.settings(&search_settings)
|
||||||
|
.build();
|
||||||
let mut model = Self {
|
let mut model = Self {
|
||||||
tracker: 0,
|
tracker: 0,
|
||||||
log: vec![],
|
log: vec![],
|
||||||
textbuf: gtk::TextBuffer::builder().build(),
|
textbuf,
|
||||||
textview: None,
|
textview: None,
|
||||||
enable_debug_view: init.enable_debug_view,
|
enable_debug_view: init.enable_debug_view,
|
||||||
searchbar: None,
|
searchbar: None,
|
||||||
search_entry: None,
|
search_entry: None,
|
||||||
dropdown: None,
|
dropdown: None,
|
||||||
|
scrolledwin: None,
|
||||||
|
search_settings,
|
||||||
|
search_ctx,
|
||||||
|
search_mark: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
@ -206,13 +300,14 @@ impl SimpleComponent for DebugView {
|
||||||
model.search_entry = Some(widgets.search_entry.clone());
|
model.search_entry = Some(widgets.search_entry.clone());
|
||||||
model.textview = Some(widgets.textview.clone());
|
model.textview = Some(widgets.textview.clone());
|
||||||
model.dropdown = Some(widgets.log_level_dropdown.clone());
|
model.dropdown = Some(widgets.log_level_dropdown.clone());
|
||||||
|
model.scrolledwin = Some(widgets.scrolledwin.clone());
|
||||||
|
|
||||||
{
|
{
|
||||||
let dd_sender = sender.clone();
|
let dd_sender = sender.clone();
|
||||||
widgets
|
widgets
|
||||||
.log_level_dropdown
|
.log_level_dropdown
|
||||||
.connect_selected_notify(move |dd| {
|
.connect_selected_notify(move |_| {
|
||||||
dd_sender.input(Self::Input::FilterLog);
|
dd_sender.input(Self::Input::FilterLog(SearchDirection::Forward));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue