Compare commits
1 Commits
main
...
feat/edito
Author | SHA1 | Date |
---|---|---|
Nefo Fortressia | 6cec9c59af |
|
@ -1,15 +0,0 @@
|
||||||
# Description
|
|
||||||
|
|
||||||
Please write down a clear and concise description of this Merge Request.
|
|
||||||
|
|
||||||
# Closes
|
|
||||||
|
|
||||||
Please write down the issues that this MR closes.
|
|
||||||
|
|
||||||
# Implementation Insight
|
|
||||||
|
|
||||||
Please write down how did you implement this in a clear and concise manner.
|
|
||||||
|
|
||||||
# TO-DOs
|
|
||||||
|
|
||||||
A list of TO-DOs of things you have did before the MR is undrafted.
|
|
|
@ -1,15 +1,3 @@
|
||||||
---
|
|
||||||
|
|
||||||
name: Bug Report
|
|
||||||
about: Report a bug affecting Echidna Code Editor
|
|
||||||
title: bug:
|
|
||||||
ref: main
|
|
||||||
labels:
|
|
||||||
|
|
||||||
- bug
|
|
||||||
- help needed
|
|
||||||
---
|
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Please write a clear and concise description of the issue you are getting.
|
Please write a clear and concise description of the issue you are getting.
|
|
@ -1,15 +1,3 @@
|
||||||
---
|
|
||||||
|
|
||||||
name: Feature Request
|
|
||||||
about: Request a new feature or support that can be implemented into Echidna.
|
|
||||||
title: "feat: "
|
|
||||||
ref: main
|
|
||||||
labels:
|
|
||||||
|
|
||||||
- enhancement
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Please write a clear and concise description of the issue you are getting.
|
Please write a clear and concise description of the issue you are getting.
|
|
@ -0,0 +1 @@
|
||||||
|
"clippy::style" = "deny"
|
|
@ -3,12 +3,9 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
pub mod imp;
|
pub mod imp;
|
||||||
use gio::Cancellable;
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::subclass::prelude::*;
|
use gtk::subclass::prelude::*;
|
||||||
use sourceview::{
|
use sourceview::{prelude::*, Buffer, FileExt as SourceFileExt, FileLoader, LanguageManager};
|
||||||
prelude::*, Buffer, FileExt as SourceFileExt, FileLoader, FileSaver, LanguageManager,
|
|
||||||
};
|
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
pub struct EchidnaCoreEditor(ObjectSubclass<imp::EchidnaCoreEditor>)
|
pub struct EchidnaCoreEditor(ObjectSubclass<imp::EchidnaCoreEditor>)
|
||||||
|
@ -24,47 +21,47 @@ impl EchidnaCoreEditor {
|
||||||
// Without cloning it, for some reasons the Rust compiler complains about &this.to_imp().sourceview not being IsA<sourceview::View>
|
// 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());
|
this_imp.minimap.set_view(&this_imp.sourceview.clone());
|
||||||
|
|
||||||
match file {
|
if file.is_some() {
|
||||||
Some(file) => {
|
let file = file.unwrap();
|
||||||
let file_location = file
|
let file_location = file
|
||||||
.location()
|
.location()
|
||||||
.expect("file is required to have a location");
|
.expect("file is required to have a location");
|
||||||
|
|
||||||
this.set_property("file", &file)
|
this.set_property("file", &file)
|
||||||
.expect("Could not set the 'file' property of EchidnaCoreEditor");
|
.expect("Could not set the 'file' property of EchidnaCoreEditor");
|
||||||
|
|
||||||
let cancellable = gio::Cancellable::new();
|
let cancellable = gio::Cancellable::new();
|
||||||
let filepath = file_location.path().expect("No filepath");
|
let filepath = file_location.path().expect("No filepath");
|
||||||
let info = file_location
|
let info = file_location
|
||||||
.query_info("*", gio::FileQueryInfoFlags::NONE, Some(&cancellable))
|
.query_info("*", gio::FileQueryInfoFlags::NONE, Some(&cancellable))
|
||||||
.expect("Could not query the info for file");
|
.expect("Could not query the info for file");
|
||||||
|
|
||||||
let content_type = info
|
let content_type = info
|
||||||
.content_type()
|
.content_type()
|
||||||
.expect(format!("It does not seem like {:?} has a type", filepath).as_str());
|
.expect(format!("It does not seem like {:?} has a type", filepath).as_str());
|
||||||
{
|
{
|
||||||
println!(
|
println!(
|
||||||
"Opened {} and found its content type is {}.",
|
"Opened {} and found its content type is {}.",
|
||||||
"file",
|
"file",
|
||||||
content_type.to_string()
|
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 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_manager = LanguageManager::new();
|
||||||
let language = language_manager.guess_language(
|
let language = language_manager.guess_language(
|
||||||
Some(&info.name().to_str().expect(
|
Some(&info.name().to_str().expect(
|
||||||
"Could not open the file because its name is not supported by Unicode.",
|
"Could not open the file because its name is not supported by Unicode.",
|
||||||
)),
|
)),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
match language {
|
match language {
|
||||||
Some(lang) => buffer.set_language(Some(&lang)),
|
Some(lang) => buffer.set_language(Some(&lang)),
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_loader: FileLoader = FileLoader::new(&buffer, &file);
|
let file_loader: FileLoader = FileLoader::new(&buffer, &file);
|
||||||
|
|
||||||
file_loader.load_async(
|
file_loader.load_async(
|
||||||
glib::Priority::default(),
|
glib::Priority::default(),
|
||||||
Some(&cancellable),
|
Some(&cancellable),
|
||||||
|_, _| {},
|
|_, _| {},
|
||||||
|
@ -74,9 +71,7 @@ impl EchidnaCoreEditor {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
@ -88,50 +83,4 @@ impl EchidnaCoreEditor {
|
||||||
pub fn file(&self) -> sourceview::File {
|
pub fn file(&self) -> sourceview::File {
|
||||||
self.property("file").expect("Could not get property 'file' of EchidnaCoreEditor").get::<sourceview::File>().expect("Could not get property 'file' of EchidnaCoreEditor because its type is not IsA<sourceview::File>")
|
self.property("file").expect("Could not get property 'file' of EchidnaCoreEditor").get::<sourceview::File>().expect("Could not get property 'file' of EchidnaCoreEditor because its type is not IsA<sourceview::File>")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_file(&self, save_as: Option<&gio::File>) -> Result<(), &str> {
|
|
||||||
let window_imp = self.to_imp();
|
|
||||||
let buffer = self.to_imp().sourceview.buffer().downcast::<Buffer>();
|
|
||||||
|
|
||||||
match buffer {
|
|
||||||
Ok(buffer) => {
|
|
||||||
let cancellable = Cancellable::new();
|
|
||||||
let mut file_saver: Option<FileSaver> = None;
|
|
||||||
let result: Result<(), &str> = match save_as {
|
|
||||||
Some(file) => {
|
|
||||||
file_saver = Some(FileSaver::with_target(&buffer, &self.file(), file));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
None => match self.file().location() {
|
|
||||||
Some(_) => {
|
|
||||||
file_saver = Some(FileSaver::new(&buffer, &self.file()));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
None => Err("The file location must exist. Please do \"Save As\""),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Err(result) => Err(result),
|
|
||||||
Ok(_) => {
|
|
||||||
file_saver.unwrap().save_async(
|
|
||||||
glib::Priority::default(),
|
|
||||||
Some(&cancellable),
|
|
||||||
|_, _| {},
|
|
||||||
|result| {
|
|
||||||
if result.is_err() {
|
|
||||||
panic!(
|
|
||||||
"Found an error while saving the file:\n{}",
|
|
||||||
result.err().expect("No error")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => Err("Can't downcast the buffer to GtkSourceBuffer."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,3 @@ impl EchidnaSidebar {
|
||||||
glib::Object::new(&[]).expect("Failed to create 'EchidnaSidebar' component.")
|
glib::Object::new(&[]).expect("Failed to create 'EchidnaSidebar' component.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EchidnaSidebar {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -36,9 +36,9 @@ impl BuildableImpl for TabLabel {
|
||||||
fn add_child(
|
fn add_child(
|
||||||
&self,
|
&self,
|
||||||
buildable: &Self::Type,
|
buildable: &Self::Type,
|
||||||
_builder: >k::Builder,
|
builder: >k::Builder,
|
||||||
child: &glib::Object,
|
child: &glib::Object,
|
||||||
_type_: Option<&str>,
|
type_: Option<&str>,
|
||||||
) {
|
) {
|
||||||
buildable.prepend(child.downcast_ref::<gtk::Widget>().unwrap());
|
buildable.prepend(child.downcast_ref::<gtk::Widget>().unwrap());
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,8 @@ impl TabLabel {
|
||||||
pub fn new<U: IsA<gtk::Widget>>(tab_label: Option<&U>) -> Self {
|
pub fn new<U: IsA<gtk::Widget>>(tab_label: Option<&U>) -> Self {
|
||||||
let this: Self = glib::Object::new(&[]).expect("Failed to create 'TabLabel' component.");
|
let this: Self = glib::Object::new(&[]).expect("Failed to create 'TabLabel' component.");
|
||||||
|
|
||||||
match tab_label {
|
if tab_label.is_some() {
|
||||||
Some(tab_label) => this.prepend(tab_label),
|
this.prepend(tab_label.unwrap());
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,20 +2,20 @@
|
||||||
* 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 crate::components::editor::EchidnaCoreEditor;
|
|
||||||
use crate::lib::prelude::*;
|
use crate::lib::prelude::*;
|
||||||
use glib::clone;
|
|
||||||
|
use crate::components::editor::EchidnaCoreEditor;
|
||||||
|
use gio::Cancellable;
|
||||||
|
use glib::{clone, Priority};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
prelude::*, subclass::prelude::*, FileChooserAction, FileChooserNative, Label, ResponseType,
|
prelude::*, subclass::prelude::*, FileChooserAction, FileChooserNative, Label, ResponseType,
|
||||||
};
|
};
|
||||||
use sourceview::{File, FileExt as SourceFileExt};
|
use sourceview::{prelude::*, Buffer, File, FileSaver};
|
||||||
|
|
||||||
pub trait FileImplementedEditor {
|
pub trait FileImplementedEditor {
|
||||||
fn action_open_file(&self);
|
fn action_open_file(&self);
|
||||||
fn open_file(notebook: >k::Notebook, file: gio::File);
|
fn open_file(notebook: >k::Notebook, file: gio::File);
|
||||||
fn action_save_file_as(&self);
|
fn action_save_file_as(&self);
|
||||||
fn action_new_file(&self);
|
|
||||||
fn action_save_file(&self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileImplementedEditor for super::EchidnaWindow {
|
impl FileImplementedEditor for super::EchidnaWindow {
|
||||||
|
@ -67,7 +67,7 @@ impl FileImplementedEditor for super::EchidnaWindow {
|
||||||
notebook.prepend_closable_page(
|
notebook.prepend_closable_page(
|
||||||
&editor_page,
|
&editor_page,
|
||||||
Some(&Label::new(Some(
|
Some(&Label::new(Some(
|
||||||
file_location
|
&file_location
|
||||||
.path()
|
.path()
|
||||||
.expect("The file's path is missing")
|
.expect("The file's path is missing")
|
||||||
.file_name()
|
.file_name()
|
||||||
|
@ -77,6 +77,7 @@ impl FileImplementedEditor for super::EchidnaWindow {
|
||||||
))),
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_save_file_as(&self) {
|
fn action_save_file_as(&self) {
|
||||||
let dialog = FileChooserNative::new(
|
let dialog = FileChooserNative::new(
|
||||||
Some("Save File As"),
|
Some("Save File As"),
|
||||||
|
@ -94,32 +95,44 @@ impl FileImplementedEditor for super::EchidnaWindow {
|
||||||
move |dialog, response| {
|
move |dialog, response| {
|
||||||
if response == ResponseType::Accept {
|
if response == ResponseType::Accept {
|
||||||
let file = dialog.file().expect("");
|
let file = dialog.file().expect("");
|
||||||
let tab: EchidnaCoreEditor = window.get_current_tab().expect("error");
|
let window_imp = window.to_imp();
|
||||||
tab.save_file(Some(&file));
|
let page: EchidnaCoreEditor;
|
||||||
}
|
|
||||||
|
match window_imp.notebook
|
||||||
|
.nth_page(
|
||||||
|
Some(window_imp.notebook
|
||||||
|
.current_page()
|
||||||
|
.expect(
|
||||||
|
"No tabs is the current tab, probably all tabs closed. No files to save"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).expect(
|
||||||
|
"Couldn't get the page of the current index. Try again."
|
||||||
|
).downcast::<EchidnaCoreEditor>() {
|
||||||
|
Ok(res) => page = res,
|
||||||
|
Err(e) => panic!("We got an error when trying to downcast the current tab page into EchidnaCoreEditor:\n{}", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer: Buffer = page.to_imp().sourceview.buffer().downcast().expect("Could not downcast the editor's buffer to GtkSourceBuffer.");
|
||||||
|
let cancellable = Cancellable::new();
|
||||||
|
|
||||||
|
let file_saver = FileSaver::with_target(
|
||||||
|
&buffer,
|
||||||
|
&page.file(), &file);
|
||||||
|
file_saver.save_async(
|
||||||
|
Priority::default(),
|
||||||
|
Some(&cancellable),
|
||||||
|
|_, _| {},
|
||||||
|
|result| {
|
||||||
|
if result.is_err() {
|
||||||
|
panic!("Found an error while saving the file:\n{}", result.err().expect("No error"))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
dialog.destroy();
|
dialog.destroy();
|
||||||
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_new_file(&self) {
|
|
||||||
let editor_page = EchidnaCoreEditor::new(None);
|
|
||||||
|
|
||||||
self.to_imp()
|
|
||||||
.notebook
|
|
||||||
.prepend_closable_page(&editor_page, Some(>k::Label::new(Some(&"Untitled"))));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action_save_file(&self) {
|
|
||||||
let page: EchidnaCoreEditor = self
|
|
||||||
.get_current_tab()
|
|
||||||
.expect("Can't find the current tab because there are no tabs.");
|
|
||||||
match page.file().location() {
|
|
||||||
Some(_) => {
|
|
||||||
page.save_file(None);
|
|
||||||
}
|
|
||||||
None => self.action_save_file_as(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,26 +122,5 @@ impl MenubarImplementedEditor for EchidnaWindow {
|
||||||
window.action_save_file_as();
|
window.action_save_file_as();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
{
|
|
||||||
let action_new_file = SimpleAction::new("new-file", None);
|
|
||||||
|
|
||||||
self.add_action(&action_new_file);
|
|
||||||
|
|
||||||
action_new_file.connect_activate(clone!(@weak self as window =>
|
|
||||||
move |_action, _variant| {
|
|
||||||
window.action_new_file();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let action_save = SimpleAction::new("save", None);
|
|
||||||
|
|
||||||
self.add_action(&action_save);
|
|
||||||
|
|
||||||
action_save.connect_activate(clone!(@weak self as window =>
|
|
||||||
move |_, _| {
|
|
||||||
window.action_save_file();
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ pub mod file;
|
||||||
pub mod imp;
|
pub mod imp;
|
||||||
pub mod menubar;
|
pub mod menubar;
|
||||||
|
|
||||||
use glib::object::{Cast, IsA};
|
use glib::object::IsA;
|
||||||
use gtk::subclass::prelude::*;
|
use gtk::subclass::prelude::*;
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
|
@ -28,25 +28,4 @@ impl EchidnaWindow {
|
||||||
pub fn to_imp(&self) -> &imp::EchidnaWindow {
|
pub fn to_imp(&self) -> &imp::EchidnaWindow {
|
||||||
imp::EchidnaWindow::from_instance(self)
|
imp::EchidnaWindow::from_instance(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_tab<A: IsA<gtk::Widget>>(&self) -> Result<A, &str> {
|
|
||||||
let window_imp = self.to_imp();
|
|
||||||
let nth = window_imp.notebook.current_page();
|
|
||||||
|
|
||||||
match nth {
|
|
||||||
None => Err("No tabs are currently opened, maybe there are no tabs."),
|
|
||||||
Some(nth) => {
|
|
||||||
let page = window_imp
|
|
||||||
.notebook
|
|
||||||
.nth_page(Some(nth))
|
|
||||||
.expect("Couldn't get the page of the current index.");
|
|
||||||
|
|
||||||
match page.downcast::<A>()
|
|
||||||
{
|
|
||||||
Ok(page) => Ok(page),
|
|
||||||
Err(e) => Err("Cannot downcast to type parameter A. Maybe it's not in the type you are looking for."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
use crate::components::tab_label::TabLabel;
|
use crate::components::tab_label::TabLabel;
|
||||||
use glib::IsA;
|
use glib::IsA;
|
||||||
use gtk::{prelude::*, Widget};
|
use gtk::{prelude::*, Box, Button, Widget};
|
||||||
|
|
||||||
pub trait ClosableTabImplementedNotebook {
|
pub trait ClosableTabImplementedNotebook {
|
||||||
fn prepend_closable_page<T: IsA<Widget>, U: IsA<Widget>>(
|
fn prepend_closable_page<T: IsA<Widget>, U: IsA<Widget>>(
|
||||||
|
|
Loading…
Reference in New Issue