Compare commits
No commits in common. "main" and "chore/description-templates" have entirely different histories.
main
...
chore/desc
|
@ -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
|
||||
|
||||
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
|
||||
|
||||
Please write a clear and concise description of the issue you are getting.
|
|
@ -1,11 +1,11 @@
|
|||
repos:
|
||||
- repo: https://github.com/commitizen-tools/commitizen
|
||||
rev: v2.20.0
|
||||
rev: master
|
||||
hooks:
|
||||
- id: commitizen
|
||||
stages: [commit-msg]
|
||||
- repo: https://github.com/doublify/pre-commit-rust
|
||||
rev: v1.0
|
||||
rev: master
|
||||
hooks:
|
||||
- id: fmt
|
||||
- id: cargo-check
|
||||
|
|
|
@ -20,6 +20,12 @@ version = "1.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
|
||||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.14.7"
|
||||
|
@ -62,6 +68,12 @@ dependencies = [
|
|||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "echidna"
|
||||
version = "0.1.0"
|
||||
|
@ -75,6 +87,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"sourceview5",
|
||||
"webbrowser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -425,12 +438,36 @@ version = "0.4.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.4"
|
||||
|
@ -806,6 +843,87 @@ version = "0.9.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webbrowser"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecad156490d6b620308ed411cfee90d280b3cbd13e189ea0d3fada8acc89158a"
|
||||
dependencies = [
|
||||
"web-sys",
|
||||
"widestring",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "echidna"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -14,4 +14,5 @@ serde = { version = "^1.0.130", features = ["derive"] }
|
|||
relative-path = "^1.5.0"
|
||||
gdk = { version = "^0.3.0", package = "gdk4"}
|
||||
sourceview = { package = "sourceview5", version = "^0.1.0", git = "https://github.com/EchidnaHQ/sourceview" }
|
||||
webbrowser = { version = "^0.5.5" }
|
||||
once_cell = "1"
|
|
@ -0,0 +1 @@
|
|||
"clippy::style" = "deny"
|
|
@ -15,11 +15,10 @@
|
|||
<object class="GtkSourceView" id="sourceview">
|
||||
<property name="vexpand">1</property>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="show-line-numbers">1</property>
|
||||
<property name="show-line-marks">1</property>
|
||||
<property name="buffer">
|
||||
<object class="GtkSourceBuffer"></object>
|
||||
</property>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
@ -3,12 +3,9 @@
|
|||
* 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, FileSaver, LanguageManager,
|
||||
};
|
||||
use sourceview::{prelude::*, Buffer, FileExt as SourceFileExt, FileLoader, LanguageManager};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct EchidnaCoreEditor(ObjectSubclass<imp::EchidnaCoreEditor>)
|
||||
|
@ -24,59 +21,57 @@ 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());
|
||||
|
||||
match file {
|
||||
Some(file) => {
|
||||
let file_location = file
|
||||
.location()
|
||||
.expect("file is required to have a location");
|
||||
if file.is_some() {
|
||||
let file = file.unwrap();
|
||||
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),
|
||||
|_, _| {},
|
||||
|result| {
|
||||
if result.is_err() {
|
||||
panic!("Found an error when loading the file into the text editor's buffer. {:#?}", result.err());
|
||||
panic!(result.err());
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
this
|
||||
}
|
||||
|
@ -88,50 +83,4 @@ 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."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,4 @@
|
|||
pub mod app;
|
||||
pub mod editor;
|
||||
pub mod sidebar;
|
||||
pub mod tab_label;
|
||||
pub mod window;
|
||||
|
||||
pub use app::EchidnaEditor;
|
||||
pub use editor::EchidnaCoreEditor;
|
||||
pub use sidebar::EchidnaSidebar;
|
||||
pub use tab_label::TabLabel;
|
||||
pub use window::EchidnaWindow;
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::window::{file::*, menubar::*};
|
||||
}
|
||||
|
|
|
@ -15,9 +15,3 @@ impl EchidnaSidebar {
|
|||
glib::Object::new(&[]).expect("Failed to create 'EchidnaSidebar' component.")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EchidnaSidebar {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,71 +6,67 @@
|
|||
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<template class="EchidnaSidebar">
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<child>
|
||||
<object class="GtkStackSidebar">
|
||||
<property name="stack">sidebar_stack</property>
|
||||
<property name="stack">
|
||||
<object class="GtkStack">
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<!-- Explorer Tab -->
|
||||
<property name="name">explorer</property>
|
||||
<property name="title">Explorer</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="treeview">
|
||||
<property name="model">liststore1</property>
|
||||
<child></child>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="selection">
|
||||
<signal name="changed" handler="on_treeview_selection_changed" />
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<!-- Search Tab -->
|
||||
<property name="name">search</property>
|
||||
<property name="title">Search</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Search</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<!-- Extensions Tab -->
|
||||
<property name="name">extensions</property>
|
||||
<property name="title">Extensions</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
</object>
|
||||
</property>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="sidebar_stack">
|
||||
<property name="hhomogeneous">1</property>
|
||||
<property name="width-request">170</property>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<!-- Explorer Tab -->
|
||||
<property name="name">explorer</property>
|
||||
<property name="title">Explorer</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Explorer</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<!-- Search Tab -->
|
||||
<property name="name">search</property>
|
||||
<property name="title">Search</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Search</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<!-- Extensions Tab -->
|
||||
<property name="name">extensions</property>
|
||||
<property name="title">Extensions</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Extensions</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
</template>
|
||||
</interface>
|
|
@ -1,45 +0,0 @@
|
|||
/* 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 = "./tab-label.ui")]
|
||||
pub struct TabLabel {
|
||||
#[template_child]
|
||||
pub button: TemplateChild<gtk::Button>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for TabLabel {
|
||||
const NAME: &'static str = "TabLabel";
|
||||
type Type = super::TabLabel;
|
||||
type ParentType = gtk::Box;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
Self::bind_template(klass);
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for TabLabel {}
|
||||
impl WidgetImpl for TabLabel {}
|
||||
impl BoxImpl for TabLabel {}
|
||||
|
||||
impl BuildableImpl for TabLabel {
|
||||
fn add_child(
|
||||
&self,
|
||||
buildable: &Self::Type,
|
||||
_builder: >k::Builder,
|
||||
child: &glib::Object,
|
||||
_type_: Option<&str>,
|
||||
) {
|
||||
buildable.prepend(child.downcast_ref::<gtk::Widget>().unwrap());
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/* 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 glib::IsA;
|
||||
use gtk::prelude::*;
|
||||
use gtk::subclass::prelude::*;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct TabLabel(ObjectSubclass<imp::TabLabel>)
|
||||
@extends gtk::Box, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
||||
}
|
||||
|
||||
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.");
|
||||
|
||||
match tab_label {
|
||||
Some(tab_label) => this.prepend(tab_label),
|
||||
None => {}
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
pub fn to_imp(&self) -> &imp::TabLabel {
|
||||
imp::TabLabel::from_instance(self)
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk+" version="4.5.0" />
|
||||
<!-- 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/.
|
||||
-->
|
||||
<template class="TabLabel">
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="button">
|
||||
<property name="has-frame">0</property>
|
||||
<property name="icon-name">window-close-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -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::components::editor::EchidnaCoreEditor;
|
||||
use crate::lib::prelude::*;
|
||||
use glib::clone;
|
||||
|
||||
use crate::components::editor::EchidnaCoreEditor;
|
||||
use gio::Cancellable;
|
||||
use glib::{clone, Priority};
|
||||
use gtk::{
|
||||
prelude::*, subclass::prelude::*, FileChooserAction, FileChooserNative, Label, ResponseType,
|
||||
prelude::*, subclass::prelude::*, FileChooserAction, FileChooserDialog, Label, ResponseType,
|
||||
};
|
||||
use sourceview::{File, FileExt as SourceFileExt};
|
||||
use sourceview::{prelude::*, Buffer, File, FileSaver};
|
||||
|
||||
pub trait FileImplementedEditor {
|
||||
fn action_open_file(&self);
|
||||
fn open_file(notebook: >k::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 {
|
||||
|
@ -36,12 +36,14 @@ impl FileImplementedEditor for super::EchidnaWindow {
|
|||
Perhaps some of the last points should not be implemented in this function but rather in another function that keeps track of every files.
|
||||
*/
|
||||
fn action_open_file(&self) {
|
||||
let dialog = FileChooserNative::new(
|
||||
let dialog: FileChooserDialog = FileChooserDialog::new(
|
||||
Some("Open a file"),
|
||||
Some(self),
|
||||
FileChooserAction::Open,
|
||||
Some("Open"),
|
||||
Some("Cancel"),
|
||||
&[
|
||||
("Cancel", ResponseType::Cancel),
|
||||
("Open", ResponseType::Accept),
|
||||
],
|
||||
);
|
||||
|
||||
dialog.set_visible(true);
|
||||
|
@ -67,7 +69,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,13 +79,16 @@ impl FileImplementedEditor for super::EchidnaWindow {
|
|||
))),
|
||||
);
|
||||
}
|
||||
|
||||
fn action_save_file_as(&self) {
|
||||
let dialog = FileChooserNative::new(
|
||||
let dialog = FileChooserDialog::new(
|
||||
Some("Save File As"),
|
||||
Some(self),
|
||||
FileChooserAction::Save,
|
||||
Some("Open"),
|
||||
Some("Cancel"),
|
||||
&[
|
||||
("Cancel", ResponseType::Cancel),
|
||||
("Save", ResponseType::Accept),
|
||||
],
|
||||
);
|
||||
|
||||
dialog.set_current_name("untitled");
|
||||
|
@ -94,32 +99,44 @@ impl FileImplementedEditor for super::EchidnaWindow {
|
|||
move |dialog, response| {
|
||||
if response == ResponseType::Accept {
|
||||
let file = dialog.file().expect("");
|
||||
let tab: EchidnaCoreEditor = window.get_current_tab().expect("error");
|
||||
tab.save_file(Some(&file));
|
||||
}
|
||||
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!(format!("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!(format!("Found an error while saving the file:\n{}", result.err().expect("No error")))
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ impl MenubarImplementedEditor for EchidnaWindow {
|
|||
|
||||
about_dialog.set_license_type(gtk::License::Mpl20);
|
||||
about_dialog.set_program_name(Some("Echidna Code Editor"));
|
||||
about_dialog.set_website(Some("https://gitlab.com/EchidnaHQ/Echidna"));
|
||||
about_dialog.set_website(Some("https://github.com/EchidnaHQ/Echidna"));
|
||||
about_dialog.set_authors(&["FortressValkriye"]);
|
||||
about_dialog.set_copyright(Some("Made with by ❤️ Echidna contributors"));
|
||||
about_dialog.set_visible(true);
|
||||
|
@ -78,20 +78,18 @@ impl MenubarImplementedEditor for EchidnaWindow {
|
|||
|
||||
app.add_action(&act_report_issue);
|
||||
|
||||
act_report_issue.connect_activate(clone!(@weak self as win =>
|
||||
move |_action, _variant| {
|
||||
gtk::show_uri(Some(&win), "https://github.com/EchidnaHQ/Echidna/issues/new", gdk::CURRENT_TIME);
|
||||
}));
|
||||
act_report_issue.connect_activate(|_action, _variant| {
|
||||
webbrowser::open("https://github.com/EchidnaHQ/Echidna/issues/new");
|
||||
});
|
||||
}
|
||||
{
|
||||
let act_search_feature_requests = SimpleAction::new("search-feature-requests", None);
|
||||
|
||||
app.add_action(&act_search_feature_requests);
|
||||
|
||||
act_search_feature_requests.connect_activate(clone!(@weak self as win =>
|
||||
move |_action, _variant| {
|
||||
gtk::show_uri(Some(&win), "https://github.com/EchidnaHQ/Echidna/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement", gdk::CURRENT_TIME);
|
||||
}));
|
||||
act_search_feature_requests.connect_activate(|_action, _variant| {
|
||||
webbrowser::open("https://github.com/EchidnaHQ/Echidna/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement");
|
||||
});
|
||||
}
|
||||
{
|
||||
let act_window_close = SimpleAction::new("close", None);
|
||||
|
@ -122,26 +120,5 @@ 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();
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
* 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 file;
|
||||
pub mod imp;
|
||||
mod file;
|
||||
|
||||
mod imp;
|
||||
pub mod menubar;
|
||||
|
||||
use glib::object::{Cast, IsA};
|
||||
use glib::object::IsA;
|
||||
use gtk::subclass::prelude::*;
|
||||
|
||||
glib::wrapper! {
|
||||
|
@ -28,25 +29,4 @@ 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."),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use glib::clone;
|
|||
use glib::subclass::types::ObjectSubclassExt;
|
||||
use glib::types::Type;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{ApplicationWindow, FileChooserAction, FileChooserNative, ResponseType, TreeStore};
|
||||
use gtk::{ApplicationWindow, FileChooserAction, FileChooserDialog, ResponseType, TreeStore};
|
||||
use relative_path::RelativePath;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
@ -46,12 +46,14 @@ impl WorkspaceImplementedEditor for EchidnaEditor {
|
|||
_action: &SimpleAction,
|
||||
_variant: Option<&glib::Variant>,
|
||||
) {
|
||||
let dialog = FileChooserNative::new(
|
||||
let dialog: FileChooserDialog = FileChooserDialog::new(
|
||||
Some("Open a file"),
|
||||
Some(&window),
|
||||
FileChooserAction::Open,
|
||||
Some("Open"),
|
||||
Some("Cancel"),
|
||||
&[
|
||||
("Cancel", ResponseType::Cancel),
|
||||
("Open", ResponseType::Accept),
|
||||
],
|
||||
);
|
||||
dialog.set_visible(true);
|
||||
dialog.connect_response(clone!(@weak window, @weak app =>
|
||||
|
@ -76,7 +78,7 @@ impl WorkspaceImplementedEditor for EchidnaEditor {
|
|||
*
|
||||
* Basically, this is just the same as Open Folder, but it's many folders.
|
||||
*
|
||||
* - Open a FileChooserNative, set to only view .code-workspace files.
|
||||
* - Open a FileChooserDialog, set to only view .code-workspace files.
|
||||
* - If the user pressed cancel, destroy the dialog. If the user opened a .code-workspace file:
|
||||
* - Get the workspace file, load and parse its content, .code-workspace files are in JSON with comments. But JSON only should be fine, for now.
|
||||
* - Iterate over folders listed in the workspace file.
|
||||
|
@ -123,29 +125,30 @@ impl WorkspaceImplementedEditor for EchidnaEditor {
|
|||
self.open_folder(folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
fn recursive_add_files_into_tree_store(&self, parent_file: File, tree: &TreeStore) {
|
||||
let child_enumerate_cancellable = Cancellable::new();
|
||||
let child_files = parent_file
|
||||
.enumerate_children(
|
||||
"*",
|
||||
FileQueryInfoFlags::NONE,
|
||||
Some(&child_enumerate_cancellable),
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
fn recursive_add_files_into_tree_store(&self, parent_file: File, tree: &TreeStore) {
|
||||
let child_enumerate_cancellable = Cancellable::new();
|
||||
let child_files = parent_file
|
||||
.enumerate_children(
|
||||
"*",
|
||||
FileQueryInfoFlags::NONE,
|
||||
Some(&child_enumerate_cancellable),
|
||||
)
|
||||
.expect(
|
||||
format!(
|
||||
"Could not look up the children files of {:?} because:\n{:#?}",
|
||||
filepath
|
||||
)
|
||||
.expect(
|
||||
format!(
|
||||
"Could not look up the children files of {:?} because:\n{:#?}",
|
||||
filepath
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
let filepath = &parent_file
|
||||
.path()
|
||||
.expect("Could not get the file path of the file.");
|
||||
.as_str(),
|
||||
);
|
||||
let filepath = &parent_file
|
||||
.path()
|
||||
.expect("Could not get the file path of the file.");
|
||||
|
||||
for file_iter in files {
|
||||
let file_info = file_iter.expect();
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
* 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::components::tab_label::TabLabel;
|
||||
use glib::IsA;
|
||||
use gtk::{prelude::*, Widget};
|
||||
use gtk::{prelude::*, Box, Button, Widget};
|
||||
|
||||
pub trait ClosableTabImplementedNotebook {
|
||||
fn prepend_closable_page<T: IsA<Widget>, U: IsA<Widget>>(
|
||||
|
@ -17,24 +16,39 @@ pub trait ClosableTabImplementedNotebook {
|
|||
child: &T,
|
||||
tab_label: Option<&U>,
|
||||
) -> u32;
|
||||
|
||||
fn create_closable_tab<U: IsA<Widget>>(tab_label: Option<&U>) -> (Box, Button);
|
||||
}
|
||||
|
||||
impl ClosableTabImplementedNotebook for gtk::Notebook {
|
||||
fn create_closable_tab<U: IsA<Widget>>(tab_label: Option<&U>) -> (Box, Button) {
|
||||
let tab = Box::new(gtk::Orientation::Horizontal, 5);
|
||||
if tab_label.is_some() {
|
||||
tab.append(tab_label.unwrap());
|
||||
}
|
||||
|
||||
let button = gtk::Button::new();
|
||||
|
||||
button.set_icon_name("window-close-symbolic");
|
||||
button.set_has_frame(false);
|
||||
|
||||
tab.append(&button);
|
||||
|
||||
(tab, button)
|
||||
}
|
||||
|
||||
fn prepend_closable_page<T: IsA<Widget>, U: IsA<Widget>>(
|
||||
&self,
|
||||
child: &T,
|
||||
tab_label: Option<&U>,
|
||||
) -> u32 {
|
||||
let tab_label_widget = TabLabel::new(tab_label);
|
||||
let page = self.prepend_page(child, Some(&tab_label_widget));
|
||||
let (tab, button) = &Self::create_closable_tab(tab_label);
|
||||
let page = self.prepend_page(child, Some(tab));
|
||||
|
||||
tab_label_widget
|
||||
.to_imp()
|
||||
.button
|
||||
.connect_clicked(glib::clone!(@weak self as notebook =>
|
||||
move |_| {
|
||||
notebook.remove_page(Some(page));
|
||||
}));
|
||||
button.connect_clicked(glib::clone!(@weak self as notebook =>
|
||||
move |_| {
|
||||
notebook.remove_page(Some(page));
|
||||
}));
|
||||
|
||||
page
|
||||
}
|
||||
|
@ -44,16 +58,13 @@ impl ClosableTabImplementedNotebook for gtk::Notebook {
|
|||
child: &T,
|
||||
tab_label: Option<&U>,
|
||||
) -> u32 {
|
||||
let tab_label_widget = TabLabel::new(tab_label);
|
||||
let page = self.append_page(child, Some(&tab_label_widget));
|
||||
let (tab, button) = &Self::create_closable_tab(tab_label);
|
||||
let page = self.append_page(child, Some(tab));
|
||||
|
||||
tab_label_widget
|
||||
.to_imp()
|
||||
.button
|
||||
.connect_clicked(glib::clone!(@weak self as notebook =>
|
||||
move |_| {
|
||||
notebook.remove_page(Some(page));
|
||||
}));
|
||||
button.connect_clicked(glib::clone!(@weak self as notebook =>
|
||||
move |_| {
|
||||
notebook.remove_page(Some(page));
|
||||
}));
|
||||
|
||||
page
|
||||
}
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
pub mod closeable_tab;
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::closeable_tab::*;
|
||||
pub use super::closeable_tab::ClosableTabImplementedNotebook;
|
||||
}
|
||||
|
|
|
@ -13,8 +13,3 @@ fn main() {
|
|||
|
||||
std::process::exit(app.run());
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::components::prelude::*;
|
||||
pub use super::lib::prelude::*;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue