refactor: move editor code to a separate EchidnaCoreEditor widget
This allows for a more component-based codebase, like those in web development. Also added barebone minimap and refactored the file loading. Should have commited them in a separate commit, but I'm just used to overusing git add -p.merge-requests/8/head
parent
ffdc612e7e
commit
6144feb857
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
<interface>
|
||||||
|
<template class="EchidnaCoreEditor">
|
||||||
|
<property name="vexpand">1</property>
|
||||||
|
<property name="hexpand">1</property>
|
||||||
|
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkSourceView" id="sourceview">
|
||||||
|
<property name="vexpand">1</property>
|
||||||
|
<property name="hexpand">1</property>
|
||||||
|
<property name="buffer">
|
||||||
|
<object class="GtkSourceBuffer"></object>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSourceMap" id="minimap">
|
||||||
|
<property name="vexpand">1</property>
|
||||||
|
</object>
|
||||||
|
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::subclass::prelude::*;
|
||||||
|
use gtk::CompositeTemplate;
|
||||||
|
|
||||||
|
#[derive(Default, CompositeTemplate)]
|
||||||
|
#[template(file = "./editor.ui")]
|
||||||
|
pub struct EchidnaCoreEditor {
|
||||||
|
#[template_child]
|
||||||
|
pub minimap: TemplateChild<sourceview::Map>,
|
||||||
|
#[template_child]
|
||||||
|
pub sourceview: TemplateChild<sourceview::View>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for EchidnaCoreEditor {
|
||||||
|
const NAME: &'static str = "EchidnaCoreEditor";
|
||||||
|
type Type = super::EchidnaCoreEditor;
|
||||||
|
type ParentType = gtk::Box;
|
||||||
|
|
||||||
|
fn class_init(class: &mut Self::Class) {
|
||||||
|
Self::bind_template(class);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for EchidnaCoreEditor {}
|
||||||
|
impl WidgetImpl for EchidnaCoreEditor {}
|
||||||
|
impl BoxImpl for EchidnaCoreEditor {}
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
pub mod imp;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::subclass::prelude::*;
|
||||||
|
use sourceview::{prelude::*, Buffer, FileExt as SourceFileExt, FileLoader, LanguageManager};
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct EchidnaCoreEditor(ObjectSubclass<imp::EchidnaCoreEditor>)
|
||||||
|
@extends gtk::Box, gtk::Widget,
|
||||||
|
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl EchidnaCoreEditor {
|
||||||
|
|
||||||
|
|
||||||
|
pub fn new<P: glib::IsA<sourceview::File>>(file: Option<&P>) -> Self {
|
||||||
|
let this: Self =
|
||||||
|
glib::Object::new(&[]).expect("Failed to create 'EchidnaCoreEditor' component.");
|
||||||
|
let this_imp = this.to_imp();
|
||||||
|
// Without cloning it, for some reasons the Rust compiler complains about &this.to_imp().sourceview not being IsA<sourceview::View>
|
||||||
|
this_imp
|
||||||
|
.minimap
|
||||||
|
.set_view(&this_imp.sourceview.clone());
|
||||||
|
|
||||||
|
if file.is_some() {
|
||||||
|
let file = file.unwrap();
|
||||||
|
|
||||||
|
let file_location = file
|
||||||
|
.location()
|
||||||
|
.expect("file is required to have a location");
|
||||||
|
let cancellable = gio::Cancellable::new();
|
||||||
|
let filepath = file_location.path().expect("No filepath");
|
||||||
|
let file_info_result =
|
||||||
|
file_location.query_info("*", gio::FileQueryInfoFlags::NONE, Some(&cancellable));
|
||||||
|
match file_info_result {
|
||||||
|
Ok(info) => match info.content_type() {
|
||||||
|
Some(content_type) => {
|
||||||
|
println!(
|
||||||
|
"Opened {} and found its content type is {}.",
|
||||||
|
"file",
|
||||||
|
content_type.to_string()
|
||||||
|
);
|
||||||
|
let buffer = this_imp.sourceview.buffer().downcast::<Buffer>().expect("Cannot downcast the sourceview's buffer. Maybe the sourceview's buffer is not IsA<sourceview::Buffer>.");
|
||||||
|
let language_manager = LanguageManager::new();
|
||||||
|
let language = language_manager.guess_language(Some(&info.name().to_str().expect("Could not open the file because its name is not supported by Unicode.")), None);
|
||||||
|
|
||||||
|
|
||||||
|
match language {
|
||||||
|
Some(lang) => buffer.set_language(Some(&lang)),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_loader: FileLoader = FileLoader::new(&buffer, file);
|
||||||
|
|
||||||
|
file_loader.load_async(
|
||||||
|
glib::Priority::default(),
|
||||||
|
Some(&cancellable),
|
||||||
|
|_, _| {},
|
||||||
|
|result| {
|
||||||
|
if result.is_err() {
|
||||||
|
panic!(result.err());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => println!("It does not seem like {:?} has a type", filepath),
|
||||||
|
},
|
||||||
|
Err(e) => println!(
|
||||||
|
"Could not retrieve file information for {:?} because:\n{}",
|
||||||
|
filepath, e
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_imp(&self) -> &imp::EchidnaCoreEditor {
|
||||||
|
imp::EchidnaCoreEditor::from_instance(self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,3 +5,4 @@
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod sidebar;
|
pub mod sidebar;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
pub mod editor;
|
|
@ -2,12 +2,13 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use gio::Cancellable;
|
use crate::components::editor::EchidnaCoreEditor;
|
||||||
|
// use gio::Cancellable;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::{
|
use gtk::{
|
||||||
prelude::*, subclass::prelude::*, FileChooserAction, FileChooserDialog, Label, ResponseType,
|
prelude::*, subclass::prelude::*, FileChooserAction, FileChooserDialog, Label, ResponseType,
|
||||||
};
|
};
|
||||||
use sourceview::{prelude::*, Buffer, LanguageManager, View};
|
use sourceview::File;
|
||||||
|
|
||||||
pub trait FileImplementedEditor {
|
pub trait FileImplementedEditor {
|
||||||
fn action_open_file(&self);
|
fn action_open_file(&self);
|
||||||
|
@ -49,65 +50,28 @@ impl FileImplementedEditor for super::EchidnaWindow {
|
||||||
dialog.connect_response(clone!( @weak self as window, =>
|
dialog.connect_response(clone!( @weak self as window, =>
|
||||||
move |dialog, response| {
|
move |dialog, response| {
|
||||||
if response == ResponseType::Accept {
|
if response == ResponseType::Accept {
|
||||||
let file_option = dialog.file();
|
let file = dialog.file().expect("");
|
||||||
match file_option {
|
|
||||||
Some(file) => {
|
|
||||||
dialog.destroy();
|
|
||||||
Self::open_file(&super::imp::EchidnaWindow::from_instance(&window).notebook, file);
|
Self::open_file(&super::imp::EchidnaWindow::from_instance(&window).notebook, file);
|
||||||
},
|
|
||||||
None => {},
|
|
||||||
}
|
|
||||||
} else if response == ResponseType::Cancel {
|
} else if response == ResponseType::Cancel {
|
||||||
dialog.destroy();
|
dialog.destroy();
|
||||||
} }));
|
} }));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_file(notebook: >k::Notebook, file: gio::File) {
|
fn open_file(notebook: >k::Notebook, file_location: gio::File) {
|
||||||
let cancellable = Cancellable::new();
|
let file = File::builder().location(&file_location).build();
|
||||||
let filepath = file.path().expect("No filepath");
|
let editor_page = EchidnaCoreEditor::new(Some(&file));
|
||||||
let file_info_result =
|
|
||||||
file.query_info("*", gio::FileQueryInfoFlags::NONE, Some(&cancellable));
|
|
||||||
match file_info_result {
|
|
||||||
Ok(info) => match info.content_type() {
|
|
||||||
Some(content_type) => {
|
|
||||||
println!(
|
|
||||||
"Opened {} and found its content type is {}.",
|
|
||||||
"file",
|
|
||||||
content_type.to_string()
|
|
||||||
);
|
|
||||||
let content_cancellable = Cancellable::new();
|
|
||||||
let file_content = file.load_contents(Some(&content_cancellable));
|
|
||||||
match file_content {
|
|
||||||
Ok(content) => {
|
|
||||||
let (int_vec, _text_option) = content;
|
|
||||||
let language_manager = LanguageManager::new();
|
|
||||||
let language = language_manager.guess_language(Some(&info.name().to_str().expect("Could not open the file because its name is not supported by Unicode.")), None);
|
|
||||||
|
|
||||||
let buffer = Buffer::new(None);
|
|
||||||
buffer.set_text(std::str::from_utf8(&int_vec).expect(format!("Could not parse the contents of {:?} as it's unsupported by UTF-8", filepath).as_str()));
|
|
||||||
match language {
|
|
||||||
Some(lang) => buffer.set_language(Some(&lang)),
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let sourceview = View::with_buffer(&buffer);
|
|
||||||
|
|
||||||
notebook.prepend_page(
|
notebook.prepend_page(
|
||||||
&sourceview,
|
&editor_page,
|
||||||
Some(&Label::new(Some(
|
Some(&Label::new(Some(
|
||||||
&info.name().to_str().expect("Could not parse file's name"),
|
&file_location
|
||||||
|
.path()
|
||||||
|
.expect("The file's path is missing")
|
||||||
|
.file_name()
|
||||||
|
.expect("Could not get the file name, as it ends with ..")
|
||||||
|
.to_str()
|
||||||
|
.expect("Could not parse the file name, as it is not a valid Unicode."),
|
||||||
))),
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(e) => println!("Could not open {:?} because:\n{:#?}", filepath, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => println!("It does not seem like {:?} has a type", filepath),
|
|
||||||
},
|
|
||||||
Err(e) => println!(
|
|
||||||
"Could not retrieve file information for {:?} because:\n{}",
|
|
||||||
filepath, e
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue