Compare commits

...

20 Commits

Author SHA1 Message Date
Nefo Fortressia 60fc36c852
chore: migrate issue and PR templates to Gitea format
Also add metadata to the issues and the PR template.

GitLab doesn't use file-based configuration for the PR template,
so the template is added here.

See https://docs.gitea.io/en-us/issue-pull-request-templates/
2021-12-28 10:31:15 +07:00
Nefo Fortressia 4a152416ff
feat: implement creating a new file
Merge branch 'feat/new-file' from
https://gitea.treehouse.systems/fortressia/echidna.git

See #20
2021-12-28 08:59:21 +07:00
Nefo Fortressia 319bc3914b
Merge branch 'main' into feat/new-file 2021-12-28 08:55:01 +07:00
Nefo Fortressia e077e690f2
fix: panic! expecting &str in editor/mod.rs
In Rust 2021, panic! automatically formats the message.
Because of this, the panic! macro complained about the argument not being &str
as format! returns a String instead.
2021-12-28 08:37:09 +07:00
Nefo Fortressia c6da158bb6
feat: implement saving file normally
Merge branch 'feat/save' of https://gitea.treehouse.systems/fortressia/echidna

See #21
2021-12-27 21:03:08 +07:00
Nefo Fortressia f785463540
refactor: refactor things with Clippy's help
Merge branch 'refactor/clippy' of https://gitea.treehouse.systems/fortressia/echidna

See #23
2021-12-27 20:59:09 +07:00
Nefo Fortressia e0d353a10b
refactor: remove double pointer in setting a file's tab title
This is because the file name is an &OsStr converted to &str and thus already a pointer.
2021-12-18 17:34:27 +07:00
Nefo Fortressia b53915afa0
refactor: set unused parameters in TabLabel as unused 2021-12-18 17:30:28 +07:00
Nefo Fortressia 9b99932fdd
refactor: use match when processing EchidnaCoreEditor's initial file
Just like in 8e31be7.
2021-12-18 17:29:39 +07:00
Nefo Fortressia 8e31be7e47
refactor: don't unwrap tab label' childs and use match when prepending it
If statements are for booleans, matches are intended for Options like these.

See: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap
2021-12-18 17:21:50 +07:00
Nefo Fortressia c8fd305d90
refactor: removed unused imports in closeable_tab.rs 2021-12-18 17:17:53 +07:00
Nefo Fortressia 10ae0cf783
feat: implement Default for EchidnaSidebar
EchidnaSidebar doesn't take any parameters, thus allowing Default to be easily implemented on it.
2021-12-18 17:17:15 +07:00
Nefo Fortressia 9e06e96fed
chore: remove clippy.toml
Clippy categories can only be configured trough parameters and clippy macros.

They can't be configured in config files.
2021-12-18 17:16:16 +07:00
Nefo Fortressia 5e574299f3 Merge branch 'main' into 'feat/save'
# Conflicts:
#   src/components/window/file.rs
2021-12-18 04:53:27 +00:00
Nefo Fortressia d8c7f104ab
feat: implement "Save" menu 2021-12-16 14:06:33 +07:00
Nefo Fortressia 52d1bdf7db
refactor: move the code for saving files to EchidnaCoreEditor
Additionally add support for normal saving ("Save File").

This change gives support for saving files other than the current one.

EchidnaWindow.save_file_as() was only able to save the current tab opened.
2021-12-16 14:04:53 +07:00
Nefo Fortressia ce8fb5dd12
refactor: separate get_current_tab() from save_file_as()
EchidnaWindow.get_current_tab() can now be used independently.

This will help with reusing it in another places. I think it will be used a lot.

Additionally, it will return Result<A, &str> instead of panicking, as it's a utils function now and should never panic.
2021-12-16 11:08:42 +07:00
Nefo Fortressia d480fdb02b
refactor: move saving file code to save_file_as()
save_file_as() can now be run independently.
2021-12-15 19:45:19 +07:00
Nefo Fortressia 87fdfc8d9c
feat: implement win.new-file action 2021-11-14 10:29:51 +07:00
Nefo Fortressia 0bd8b34cfe
Merge branch 'refactor/exports' into feat/new-file 2021-11-14 10:05:59 +07:00
12 changed files with 210 additions and 85 deletions

View File

@ -0,0 +1,15 @@
# 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.

View File

@ -1,3 +1,15 @@
---
name: Bug Report
about: Report a bug affecting Echidna Code Editor
title: bug:
ref: main
labels:
- bug
- help needed
---
## Description
Please write a clear and concise description of the issue you are getting.

View File

@ -1,3 +1,15 @@
---
name: Feature Request
about: Request a new feature or support that can be implemented into Echidna.
title: "feat: "
ref: main
labels:
- enhancement
---
## Description
Please write a clear and concise description of the issue you are getting.

View File

@ -1 +0,0 @@
"clippy::style" = "deny"

View File

@ -3,9 +3,12 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
pub mod imp;
use gio::Cancellable;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use sourceview::{prelude::*, Buffer, FileExt as SourceFileExt, FileLoader, LanguageManager};
use sourceview::{
prelude::*, Buffer, FileExt as SourceFileExt, FileLoader, FileSaver, LanguageManager,
};
glib::wrapper! {
pub struct EchidnaCoreEditor(ObjectSubclass<imp::EchidnaCoreEditor>)
@ -21,47 +24,47 @@ impl EchidnaCoreEditor {
// 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");
match file {
Some(file) => {
let file_location = file
.location()
.expect("file is required to have a location");
this.set_property("file", &file)
.expect("Could not set the 'file' property of EchidnaCoreEditor");
this.set_property("file", &file)
.expect("Could not set the 'file' property of EchidnaCoreEditor");
let cancellable = gio::Cancellable::new();
let filepath = file_location.path().expect("No filepath");
let info = file_location
.query_info("*", gio::FileQueryInfoFlags::NONE, Some(&cancellable))
.expect("Could not query the info for file");
let cancellable = gio::Cancellable::new();
let filepath = file_location.path().expect("No filepath");
let info = file_location
.query_info("*", gio::FileQueryInfoFlags::NONE, Some(&cancellable))
.expect("Could not query the info for file");
let content_type = info
.content_type()
.expect(format!("It does not seem like {:?} has a type", filepath).as_str());
{
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,
);
let content_type = info
.content_type()
.expect(format!("It does not seem like {:?} has a type", filepath).as_str());
{
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 => {}
}
match language {
Some(lang) => buffer.set_language(Some(&lang)),
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(),
Some(&cancellable),
|_, _| {},
@ -71,7 +74,9 @@ impl EchidnaCoreEditor {
}
},
);
}
}
None => {}
}
this
}
@ -83,4 +88,50 @@ impl EchidnaCoreEditor {
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>")
}
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."),
}
}
}

View File

@ -15,3 +15,9 @@ impl EchidnaSidebar {
glib::Object::new(&[]).expect("Failed to create 'EchidnaSidebar' component.")
}
}
impl Default for EchidnaSidebar {
fn default() -> Self {
Self::new()
}
}

View File

@ -36,9 +36,9 @@ impl BuildableImpl for TabLabel {
fn add_child(
&self,
buildable: &Self::Type,
builder: &gtk::Builder,
_builder: &gtk::Builder,
child: &glib::Object,
type_: Option<&str>,
_type_: Option<&str>,
) {
buildable.prepend(child.downcast_ref::<gtk::Widget>().unwrap());
}

View File

@ -17,8 +17,9 @@ impl TabLabel {
pub fn new<U: IsA<gtk::Widget>>(tab_label: Option<&U>) -> Self {
let this: Self = glib::Object::new(&[]).expect("Failed to create 'TabLabel' component.");
if tab_label.is_some() {
this.prepend(tab_label.unwrap());
match tab_label {
Some(tab_label) => this.prepend(tab_label),
None => {}
}
this
}

View File

@ -2,20 +2,20 @@
* 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 crate::lib::prelude::*;
use crate::components::editor::EchidnaCoreEditor;
use gio::Cancellable;
use glib::{clone, Priority};
use crate::lib::prelude::*;
use glib::clone;
use gtk::{
prelude::*, subclass::prelude::*, FileChooserAction, FileChooserNative, Label, ResponseType,
};
use sourceview::{prelude::*, Buffer, File, FileSaver};
use sourceview::{File, FileExt as SourceFileExt};
pub trait FileImplementedEditor {
fn action_open_file(&self);
fn open_file(notebook: &gtk::Notebook, file: gio::File);
fn action_save_file_as(&self);
fn action_new_file(&self);
fn action_save_file(&self);
}
impl FileImplementedEditor for super::EchidnaWindow {
@ -67,7 +67,7 @@ impl FileImplementedEditor for super::EchidnaWindow {
notebook.prepend_closable_page(
&editor_page,
Some(&Label::new(Some(
&file_location
file_location
.path()
.expect("The file's path is missing")
.file_name()
@ -77,7 +77,6 @@ impl FileImplementedEditor for super::EchidnaWindow {
))),
);
}
fn action_save_file_as(&self) {
let dialog = FileChooserNative::new(
Some("Save File As"),
@ -95,44 +94,32 @@ impl FileImplementedEditor for super::EchidnaWindow {
move |dialog, response| {
if response == ResponseType::Accept {
let file = dialog.file().expect("");
let window_imp = window.to_imp();
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"))
}
});
}
let tab: EchidnaCoreEditor = window.get_current_tab().expect("error");
tab.save_file(Some(&file));
}
dialog.destroy();
}));
}
fn action_new_file(&self) {
let editor_page = EchidnaCoreEditor::new(None);
self.to_imp()
.notebook
.prepend_closable_page(&editor_page, Some(&gtk::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(),
}
}
}

View File

@ -122,5 +122,26 @@ impl MenubarImplementedEditor for EchidnaWindow {
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();
}
));
}
}
}

View File

@ -6,7 +6,7 @@ pub mod file;
pub mod imp;
pub mod menubar;
use glib::object::IsA;
use glib::object::{Cast, IsA};
use gtk::subclass::prelude::*;
glib::wrapper! {
@ -28,4 +28,25 @@ impl EchidnaWindow {
pub fn to_imp(&self) -> &imp::EchidnaWindow {
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."),
}
}
}
}
}

View File

@ -4,7 +4,7 @@
use crate::components::tab_label::TabLabel;
use glib::IsA;
use gtk::{prelude::*, Box, Button, Widget};
use gtk::{prelude::*, Widget};
pub trait ClosableTabImplementedNotebook {
fn prepend_closable_page<T: IsA<Widget>, U: IsA<Widget>>(