init
This commit is contained in:
commit
be6653be6f
15 changed files with 829 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[submodule "ts/tree-sitter-json"]
|
||||||
|
path = ts/tree-sitter-json
|
||||||
|
url = https://github.com/tree-sitter/tree-sitter-json
|
||||||
|
[submodule "ts/tree-sitter-lua"]
|
||||||
|
path = ts/tree-sitter-lua
|
||||||
|
url = https://github.com/MunifTanjim/tree-sitter-lua
|
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "luna"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.53"
|
||||||
|
mlua = { version = "0.7.3", features = ["luajit", "serialize", "send"] }
|
||||||
|
relm4 = { version = "0.4.2", features = ["macros"] }
|
||||||
|
serde_json = "1.0.78"
|
||||||
|
tree-sitter = "0.20.4"
|
||||||
|
tree-sitter-highlight = "0.20.1"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cc = "1.0.72"
|
7
assets/style.css
Normal file
7
assets/style.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
textview text {
|
||||||
|
background-color: #2a2c39;
|
||||||
|
}
|
||||||
|
|
||||||
|
textview {
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
14
build.rs
Normal file
14
build.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
fn main() {
|
||||||
|
cc::Build::new()
|
||||||
|
.include("ts/tree-sitter-json")
|
||||||
|
.file("ts/tree-sitter-json/src/parser.c")
|
||||||
|
.compile("tree-sitter-json");
|
||||||
|
|
||||||
|
cc::Build::new()
|
||||||
|
.include("ts/tree-sitter-lua")
|
||||||
|
.files([
|
||||||
|
"ts/tree-sitter-lua/src/parser.c",
|
||||||
|
"ts/tree-sitter-lua/src/scanner.c",
|
||||||
|
])
|
||||||
|
.compile("tree-sitter-lua");
|
||||||
|
}
|
12
rustfmt.toml
Normal file
12
rustfmt.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
unstable_features = true
|
||||||
|
binop_separator = "Back"
|
||||||
|
format_code_in_doc_comments = true
|
||||||
|
format_macro_matchers = true
|
||||||
|
format_strings = true
|
||||||
|
imports_layout = "HorizontalVertical"
|
||||||
|
match_block_trailing_comma = true
|
||||||
|
merge_imports = true
|
||||||
|
normalize_comments = true
|
||||||
|
use_field_init_shorthand = true
|
||||||
|
use_try_shorthand = true
|
||||||
|
wrap_comments = true
|
99
src/hl.rs
Normal file
99
src/hl.rs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
use relm4::gtk::{prelude::*, TextBuffer};
|
||||||
|
use tree_sitter::Language;
|
||||||
|
use tree_sitter_highlight::{Highlight, HighlightConfiguration, HighlightEvent};
|
||||||
|
|
||||||
|
const HIGHLIGHT_NAMES: &[&str] = &[
|
||||||
|
"attribute",
|
||||||
|
"comment",
|
||||||
|
"conditional",
|
||||||
|
"constant",
|
||||||
|
"function",
|
||||||
|
"function.builtin",
|
||||||
|
"keyword",
|
||||||
|
"label",
|
||||||
|
"number",
|
||||||
|
"operator",
|
||||||
|
"punctuation",
|
||||||
|
"repeat",
|
||||||
|
"string",
|
||||||
|
"type",
|
||||||
|
"variable",
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const JSON_HL_QUERY: &str = include_str!("../ts/json_highlights.scm");
|
||||||
|
pub const LUA_HL_QUERY: &str = include_str!("../ts/lua_highlights.scm");
|
||||||
|
|
||||||
|
pub fn highlight_text_buffer(buf: &TextBuffer, lang: Language, highlight_query: &str) {
|
||||||
|
buf.remove_all_tags(&buf.start_iter(), &buf.end_iter());
|
||||||
|
|
||||||
|
let mut conf = HighlightConfiguration::new(lang, highlight_query, "", "").unwrap();
|
||||||
|
conf.configure(HIGHLIGHT_NAMES);
|
||||||
|
|
||||||
|
let table = buf.tag_table();
|
||||||
|
let tag = |name: &str| table.lookup(name).expect("missing text tag!");
|
||||||
|
|
||||||
|
let txt = buf.text(&buf.start_iter(), &buf.end_iter(), false);
|
||||||
|
crate::HIGHLIGHTER.with(|hl| {
|
||||||
|
let mut hl = hl.borrow_mut();
|
||||||
|
let hls = hl.highlight(&conf, txt.as_bytes(), None, |_| None);
|
||||||
|
|
||||||
|
if let Ok(hls) = hls.and_then(|hls| hls.collect::<Result<Vec<_>, _>>()) {
|
||||||
|
let mut acc = HighlightAcc::default();
|
||||||
|
|
||||||
|
for hl in hls {
|
||||||
|
acc.push(hl);
|
||||||
|
}
|
||||||
|
|
||||||
|
for Span {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
hl: Highlight(hl),
|
||||||
|
} in acc.spans
|
||||||
|
{
|
||||||
|
buf.apply_tag(
|
||||||
|
&tag(HIGHLIGHT_NAMES[hl]),
|
||||||
|
&buf.iter_at_offset(start as i32),
|
||||||
|
&buf.iter_at_offset(end as i32),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.apply_tag(&tag("error"), &buf.start_iter(), &buf.end_iter());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct HighlightAcc {
|
||||||
|
prev_pos: usize,
|
||||||
|
pos: usize,
|
||||||
|
cur_hl: Option<Highlight>,
|
||||||
|
pub spans: Vec<Span>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HighlightAcc {
|
||||||
|
pub fn push(&mut self, ev: HighlightEvent) {
|
||||||
|
match ev {
|
||||||
|
HighlightEvent::Source { end, .. } => {
|
||||||
|
self.prev_pos = self.pos;
|
||||||
|
self.pos = end;
|
||||||
|
},
|
||||||
|
HighlightEvent::HighlightStart(hl) => self.cur_hl = Some(hl),
|
||||||
|
HighlightEvent::HighlightEnd => {
|
||||||
|
if let Some(hl) = self.cur_hl.take() {
|
||||||
|
self.spans.push(Span {
|
||||||
|
start: self.prev_pos,
|
||||||
|
end: self.pos,
|
||||||
|
hl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Span {
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
hl: Highlight,
|
||||||
|
}
|
34
src/lua.rs
Normal file
34
src/lua.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use mlua::{DeserializeOptions, Function, Lua, LuaSerdeExt, Value};
|
||||||
|
|
||||||
|
pub fn try_eval(lua: &Lua, src: &str) -> mlua::Result<(String, String)> {
|
||||||
|
lua.scope(|s| {
|
||||||
|
let output = Rc::new(RefCell::new(String::new()));
|
||||||
|
let output_ = Rc::clone(&output);
|
||||||
|
let print = s.create_function(move |lua, x: Value| {
|
||||||
|
let s = lua
|
||||||
|
.globals()
|
||||||
|
.get::<_, Function>("tostring")?
|
||||||
|
.call::<_, String>(x)?;
|
||||||
|
|
||||||
|
let mut output = output_.borrow_mut();
|
||||||
|
output.push_str(&s);
|
||||||
|
output.push('\n');
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
lua.globals().set("print", print)?;
|
||||||
|
|
||||||
|
let res = lua.from_value_with::<serde_json::Value>(
|
||||||
|
lua.load(src).eval()?,
|
||||||
|
DeserializeOptions::new()
|
||||||
|
.deny_unsupported_types(false)
|
||||||
|
.deny_recursive_tables(false),
|
||||||
|
)?;
|
||||||
|
let res = serde_json::to_string_pretty(&res).unwrap();
|
||||||
|
|
||||||
|
Ok((output.take(), res))
|
||||||
|
})
|
||||||
|
}
|
27
src/main.rs
Normal file
27
src/main.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use relm4::{gtk, RelmApp};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use tree_sitter_highlight::Highlighter;
|
||||||
|
use tree_sitter::Language;
|
||||||
|
|
||||||
|
mod hl;
|
||||||
|
mod lua;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
pub static HIGHLIGHTER: RefCell<Highlighter> = RefCell::new(Highlighter::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
gtk::init()?;
|
||||||
|
relm4::set_global_css(include_bytes!("../assets/style.css"));
|
||||||
|
let model = ui::AppModel::new()?;
|
||||||
|
let app = RelmApp::new(model);
|
||||||
|
app.run();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn tree_sitter_json() -> Language;
|
||||||
|
fn tree_sitter_lua() -> Language;
|
||||||
|
}
|
116
src/ui/entry.rs
Normal file
116
src/ui/entry.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
use relm4::{
|
||||||
|
factory::{DynamicIndex, FactoryPrototype, FactoryVecDeque},
|
||||||
|
gtk::{self, prelude::*, Orientation, TextBuffer},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::hl::{self, highlight_text_buffer};
|
||||||
|
|
||||||
|
use super::AppMsg;
|
||||||
|
|
||||||
|
pub enum Entry {
|
||||||
|
Error(TextBuffer),
|
||||||
|
LuaData {
|
||||||
|
src: TextBuffer,
|
||||||
|
out: String,
|
||||||
|
result: TextBuffer,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FactoryPrototype for Entry {
|
||||||
|
type Factory = FactoryVecDeque<Self>;
|
||||||
|
type Widgets = EntryWidgets;
|
||||||
|
type View = gtk::Box;
|
||||||
|
type Msg = AppMsg;
|
||||||
|
type Root = gtk::Box;
|
||||||
|
|
||||||
|
fn position(&self, _key: &DynamicIndex) {}
|
||||||
|
|
||||||
|
fn init_view(&self, _key: &DynamicIndex, _sender: relm4::Sender<Self::Msg>) -> Self::Widgets {
|
||||||
|
let (first_buf, second_buf) = match self {
|
||||||
|
Self::Error(e) => (e, None),
|
||||||
|
Self::LuaData { src, result, .. } => (src, Some(result)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(json) = second_buf {
|
||||||
|
highlight_text_buffer(
|
||||||
|
first_buf,
|
||||||
|
unsafe { crate::tree_sitter_lua() },
|
||||||
|
hl::LUA_HL_QUERY,
|
||||||
|
);
|
||||||
|
|
||||||
|
highlight_text_buffer(
|
||||||
|
json,
|
||||||
|
unsafe { crate::tree_sitter_json() },
|
||||||
|
hl::JSON_HL_QUERY,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
first_buf.apply_tag(
|
||||||
|
&first_buf.tag_table().lookup("error").unwrap(),
|
||||||
|
&first_buf.start_iter(),
|
||||||
|
&first_buf.end_iter(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let main_box = gtk::Box::new(Orientation::Vertical, 5);
|
||||||
|
main_box.set_valign(gtk::Align::Start);
|
||||||
|
main_box.set_baseline_position(gtk::BaselinePosition::Top);
|
||||||
|
|
||||||
|
let first_view = gtk::TextView::builder()
|
||||||
|
.height_request(20)
|
||||||
|
.editable(false)
|
||||||
|
.monospace(true)
|
||||||
|
.buffer(first_buf)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let second_view = match self {
|
||||||
|
Self::LuaData { out, .. } if !out.is_empty() => {
|
||||||
|
let buf = gtk::TextBuffer::new(None);
|
||||||
|
buf.set_text(out);
|
||||||
|
Some(
|
||||||
|
gtk::TextView::builder()
|
||||||
|
.height_request(20)
|
||||||
|
.editable(false)
|
||||||
|
.monospace(true)
|
||||||
|
.buffer(&buf)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let third_view = second_buf.map(|b| {
|
||||||
|
gtk::TextView::builder()
|
||||||
|
.height_request(20)
|
||||||
|
.editable(false)
|
||||||
|
.monospace(true)
|
||||||
|
.buffer(b)
|
||||||
|
.build()
|
||||||
|
});
|
||||||
|
|
||||||
|
main_box.append(&first_view);
|
||||||
|
if let Some(v) = second_view {
|
||||||
|
main_box.append(&v);
|
||||||
|
}
|
||||||
|
if let Some(v) = third_view {
|
||||||
|
main_box.append(&v);
|
||||||
|
}
|
||||||
|
|
||||||
|
main_box.set_visible(true);
|
||||||
|
|
||||||
|
|
||||||
|
EntryWidgets { main_box }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _key: &DynamicIndex, _widgets: &Self::Widgets) {
|
||||||
|
// This widget is never updated :P
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root_widget(widgets: &Self::Widgets) -> &Self::Root {
|
||||||
|
&widgets.main_box
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EntryWidgets {
|
||||||
|
main_box: gtk::Box,
|
||||||
|
}
|
283
src/ui/mod.rs
Normal file
283
src/ui/mod.rs
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use entry::Entry;
|
||||||
|
use mlua::Lua;
|
||||||
|
use relm4::{
|
||||||
|
factory::FactoryVecDeque,
|
||||||
|
gtk::{self, gdk, prelude::*, Inhibit, Orientation, TextBuffer, TextTag, TextTagTable},
|
||||||
|
send,
|
||||||
|
AppUpdate,
|
||||||
|
Model,
|
||||||
|
WidgetPlus,
|
||||||
|
Widgets,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
hl::{self, highlight_text_buffer},
|
||||||
|
lua::try_eval,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod entry;
|
||||||
|
|
||||||
|
pub struct AppModel {
|
||||||
|
lua: Arc<Mutex<Lua>>,
|
||||||
|
input: TextBuffer,
|
||||||
|
entries: FactoryVecDeque<Entry>,
|
||||||
|
loading: bool,
|
||||||
|
history: Vec<String>,
|
||||||
|
history_idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppModel {
|
||||||
|
pub fn new() -> anyhow::Result<Self> {
|
||||||
|
let table = TextTagTable::new();
|
||||||
|
|
||||||
|
fn tag(name: &str, color: &str) -> TextTag {
|
||||||
|
TextTag::builder().name(name).foreground(color).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
table.add(
|
||||||
|
&TextTag::builder()
|
||||||
|
.name("error")
|
||||||
|
.foreground("red")
|
||||||
|
.background("black")
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
|
||||||
|
table.add(&tag("attribute", "#50fa7b"));
|
||||||
|
table.add(&tag("comment", "#6272a4"));
|
||||||
|
table.add(&tag("conditional", "#ff79c6"));
|
||||||
|
table.add(&tag("constant", "#6be5fd"));
|
||||||
|
table.add(&tag("function", "#50fa7b"));
|
||||||
|
table.add(&tag("function.builtin", "#8be9fd"));
|
||||||
|
table.add(&tag("keyword", "#ff79c6"));
|
||||||
|
table.add(&tag("label", "#bd93f9"));
|
||||||
|
table.add(&tag("number", "#bd93f9"));
|
||||||
|
table.add(&tag("operator", "#ff79c6"));
|
||||||
|
table.add(&tag("punctuation", "#f8f8f2"));
|
||||||
|
table.add(&tag("repeat", "#ff79c6"));
|
||||||
|
table.add(&tag("string", "#f1fa8c"));
|
||||||
|
table.add(&tag("type", "#8be9fd"));
|
||||||
|
table.add(&tag("variable", "#f8f8f2"));
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
lua: unsafe { Arc::new(Mutex::new(Lua::unsafe_new())) },
|
||||||
|
input: TextBuffer::new(Some(&table)),
|
||||||
|
entries: FactoryVecDeque::new(),
|
||||||
|
loading: false,
|
||||||
|
history: Vec::new(),
|
||||||
|
history_idx: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppUpdate for AppModel {
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
msg: Self::Msg,
|
||||||
|
_components: &Self::Components,
|
||||||
|
sender: relm4::Sender<Self::Msg>,
|
||||||
|
) -> bool {
|
||||||
|
match msg {
|
||||||
|
AppMsg::Eval => {
|
||||||
|
self.loading = true;
|
||||||
|
let src = self
|
||||||
|
.input
|
||||||
|
.text(&self.input.start_iter(), &self.input.end_iter(), false)
|
||||||
|
.to_string();
|
||||||
|
let lua = Arc::clone(&self.lua);
|
||||||
|
thread::spawn(move || match try_eval(&*lua.lock().unwrap(), &src) {
|
||||||
|
Ok((out, r)) => {
|
||||||
|
send!(
|
||||||
|
sender,
|
||||||
|
AppMsg::AddEntry(StringEntry::LuaData {
|
||||||
|
src,
|
||||||
|
result: r,
|
||||||
|
out,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
send!(sender, AppMsg::ClearInput);
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(e) => send!(sender, AppMsg::AddEntry(StringEntry::Error(e.to_string()))),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
AppMsg::AddEntry(e) => {
|
||||||
|
self.entries.push_front(match e {
|
||||||
|
StringEntry::Error(e) => Entry::Error(
|
||||||
|
gtk::TextBuffer::builder()
|
||||||
|
.tag_table(&self.input.tag_table())
|
||||||
|
.text(&e)
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
StringEntry::LuaData { src, out, result } => Entry::LuaData {
|
||||||
|
src: gtk::TextBuffer::builder()
|
||||||
|
.tag_table(&self.input.tag_table())
|
||||||
|
.text(&src)
|
||||||
|
.build(),
|
||||||
|
result: gtk::TextBuffer::builder()
|
||||||
|
.tag_table(&self.input.tag_table())
|
||||||
|
.text(&result)
|
||||||
|
.build(),
|
||||||
|
out,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
self.loading = false;
|
||||||
|
},
|
||||||
|
AppMsg::ClearInput => {
|
||||||
|
let input = self
|
||||||
|
.input
|
||||||
|
.text(&self.input.start_iter(), &self.input.end_iter(), false)
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if !input.is_empty() {
|
||||||
|
self.history.push(input);
|
||||||
|
}
|
||||||
|
self.history_idx += 1;
|
||||||
|
self.input.set_text("");
|
||||||
|
},
|
||||||
|
AppMsg::ClearEntries => self.entries.clear(),
|
||||||
|
AppMsg::InputUpdate => highlight_text_buffer(
|
||||||
|
&self.input,
|
||||||
|
unsafe { crate::tree_sitter_lua() },
|
||||||
|
hl::LUA_HL_QUERY,
|
||||||
|
),
|
||||||
|
AppMsg::History(HistoryChange::Prev) => {
|
||||||
|
if self.history.len() == self.history_idx {
|
||||||
|
let input = self
|
||||||
|
.input
|
||||||
|
.text(&self.input.start_iter(), &self.input.end_iter(), false)
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if !input.is_empty() {
|
||||||
|
self.history.push(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(idx) = self.history_idx.checked_sub(1) {
|
||||||
|
self.history_idx = idx;
|
||||||
|
self.input.set_text(&self.history[idx]);
|
||||||
|
send!(sender, AppMsg::InputUpdate);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AppMsg::History(HistoryChange::Next) => {
|
||||||
|
match (self.history_idx + 1).cmp(&self.history.len()) {
|
||||||
|
std::cmp::Ordering::Less => {
|
||||||
|
self.history_idx += 1;
|
||||||
|
self.input.set_text(&self.history[self.history_idx]);
|
||||||
|
},
|
||||||
|
std::cmp::Ordering::Equal => {
|
||||||
|
self.history_idx += 1;
|
||||||
|
self.input.set_text("");
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
send!(sender, AppMsg::InputUpdate);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model for AppModel {
|
||||||
|
type Msg = AppMsg;
|
||||||
|
type Widgets = AppWidgets;
|
||||||
|
type Components = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum StringEntry {
|
||||||
|
Error(String),
|
||||||
|
LuaData {
|
||||||
|
src: String,
|
||||||
|
out: String,
|
||||||
|
result: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AppMsg {
|
||||||
|
Eval,
|
||||||
|
AddEntry(StringEntry),
|
||||||
|
ClearInput,
|
||||||
|
ClearEntries,
|
||||||
|
InputUpdate,
|
||||||
|
History(HistoryChange),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HistoryChange {
|
||||||
|
Next,
|
||||||
|
Prev,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::widget(pub)]
|
||||||
|
impl Widgets<AppModel, ()> for AppWidgets {
|
||||||
|
view! {
|
||||||
|
gtk::ApplicationWindow {
|
||||||
|
set_title: Some("luna"),
|
||||||
|
set_default_width: 300,
|
||||||
|
set_default_height: 200,
|
||||||
|
|
||||||
|
set_child = Some(>k::Box) {
|
||||||
|
set_orientation: Orientation::Vertical,
|
||||||
|
set_margin_all: 5,
|
||||||
|
set_spacing: 5,
|
||||||
|
|
||||||
|
append: scroll = >k::ScrolledWindow {
|
||||||
|
set_child = Some(>k::Box) {
|
||||||
|
set_orientation: Orientation::Vertical,
|
||||||
|
set_hexpand: true,
|
||||||
|
set_vexpand: true,
|
||||||
|
set_spacing: 8,
|
||||||
|
|
||||||
|
factory!(model.entries),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
append = >k::Box {
|
||||||
|
set_orientation: Orientation::Horizontal,
|
||||||
|
|
||||||
|
append = >k::TextView {
|
||||||
|
set_hexpand: true,
|
||||||
|
set_buffer: Some(&model.input),
|
||||||
|
set_monospace: true,
|
||||||
|
|
||||||
|
add_controller = >k::EventControllerKey {
|
||||||
|
connect_key_pressed(sender) => move |_, key, _, state| {
|
||||||
|
send!(sender, AppMsg::InputUpdate);
|
||||||
|
if !state.contains(gdk::ModifierType::SHIFT_MASK) {
|
||||||
|
match key {
|
||||||
|
gdk::Key::Return => {
|
||||||
|
send!(sender, AppMsg::Eval);
|
||||||
|
return Inhibit(true);
|
||||||
|
}
|
||||||
|
gdk::Key::Up => {
|
||||||
|
send!(sender, AppMsg::History(HistoryChange::Prev));
|
||||||
|
return Inhibit(true);
|
||||||
|
}
|
||||||
|
gdk::Key::Down => {
|
||||||
|
send!(sender, AppMsg::History(HistoryChange::Next));
|
||||||
|
return Inhibit(true);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Inhibit(false)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
append = >k::Spinner {
|
||||||
|
set_spinning: watch! { model.loading },
|
||||||
|
},
|
||||||
|
|
||||||
|
append = >k::Button {
|
||||||
|
set_label: "Clear List",
|
||||||
|
|
||||||
|
connect_clicked(sender) => move |_| send!(sender, AppMsg::ClearEntries),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
ts/json_highlights.scm
Normal file
14
ts/json_highlights.scm
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
(true) @boolean
|
||||||
|
(false) @boolean
|
||||||
|
(null) @constant.builtin
|
||||||
|
(number) @number
|
||||||
|
(pair key: (string) @label)
|
||||||
|
(pair value: (string) @string)
|
||||||
|
(array (string) @string)
|
||||||
|
(string_content (escape_sequence) @string.escape)
|
||||||
|
(ERROR) @error
|
||||||
|
"," @punctuation.delimiter
|
||||||
|
"[" @punctuation.bracket
|
||||||
|
"]" @punctuation.bracket
|
||||||
|
"{" @punctuation.bracket
|
||||||
|
"}" @punctuation.bracket
|
194
ts/lua_highlights.scm
Normal file
194
ts/lua_highlights.scm
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
;;; Builtins
|
||||||
|
|
||||||
|
[
|
||||||
|
(false)
|
||||||
|
(true)
|
||||||
|
] @boolean
|
||||||
|
|
||||||
|
(nil) @constant.builtin
|
||||||
|
|
||||||
|
((identifier) @variable.builtin
|
||||||
|
(#match? @variable.builtin "self"))
|
||||||
|
|
||||||
|
;; Keywords
|
||||||
|
|
||||||
|
"return" @keyword.return
|
||||||
|
|
||||||
|
[
|
||||||
|
"goto"
|
||||||
|
"in"
|
||||||
|
"local"
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
(label_statement) @label
|
||||||
|
|
||||||
|
(break_statement) @keyword
|
||||||
|
|
||||||
|
(do_statement
|
||||||
|
[
|
||||||
|
"do"
|
||||||
|
"end"
|
||||||
|
] @keyword)
|
||||||
|
|
||||||
|
(while_statement
|
||||||
|
[
|
||||||
|
"while"
|
||||||
|
"do"
|
||||||
|
"end"
|
||||||
|
] @repeat)
|
||||||
|
|
||||||
|
(repeat_statement
|
||||||
|
[
|
||||||
|
"repeat"
|
||||||
|
"until"
|
||||||
|
] @repeat)
|
||||||
|
|
||||||
|
(if_statement
|
||||||
|
[
|
||||||
|
"if"
|
||||||
|
"elseif"
|
||||||
|
"else"
|
||||||
|
"then"
|
||||||
|
"end"
|
||||||
|
] @conditional)
|
||||||
|
|
||||||
|
(elseif_statement
|
||||||
|
[
|
||||||
|
"elseif"
|
||||||
|
"then"
|
||||||
|
"end"
|
||||||
|
] @conditional)
|
||||||
|
|
||||||
|
(else_statement
|
||||||
|
[
|
||||||
|
"else"
|
||||||
|
"end"
|
||||||
|
] @conditional)
|
||||||
|
|
||||||
|
(for_statement
|
||||||
|
[
|
||||||
|
"for"
|
||||||
|
"do"
|
||||||
|
"end"
|
||||||
|
] @repeat)
|
||||||
|
|
||||||
|
(function_declaration
|
||||||
|
[
|
||||||
|
"function"
|
||||||
|
"end"
|
||||||
|
] @keyword)
|
||||||
|
|
||||||
|
(function_definition
|
||||||
|
[
|
||||||
|
"function"
|
||||||
|
"end"
|
||||||
|
] @keyword)
|
||||||
|
|
||||||
|
;; Operators
|
||||||
|
|
||||||
|
[
|
||||||
|
"and"
|
||||||
|
"not"
|
||||||
|
"or"
|
||||||
|
] @keyword.operator
|
||||||
|
|
||||||
|
[
|
||||||
|
"+"
|
||||||
|
"-"
|
||||||
|
"*"
|
||||||
|
"/"
|
||||||
|
"%"
|
||||||
|
"^"
|
||||||
|
"#"
|
||||||
|
"=="
|
||||||
|
"~="
|
||||||
|
"<="
|
||||||
|
">="
|
||||||
|
"<"
|
||||||
|
">"
|
||||||
|
"="
|
||||||
|
"&"
|
||||||
|
"~"
|
||||||
|
"|"
|
||||||
|
"<<"
|
||||||
|
">>"
|
||||||
|
"//"
|
||||||
|
".."
|
||||||
|
] @operator
|
||||||
|
|
||||||
|
;; Punctuations
|
||||||
|
|
||||||
|
[
|
||||||
|
";"
|
||||||
|
":"
|
||||||
|
","
|
||||||
|
"."
|
||||||
|
] @punctuation.delimiter
|
||||||
|
|
||||||
|
;; Brackets
|
||||||
|
|
||||||
|
[
|
||||||
|
"("
|
||||||
|
")"
|
||||||
|
"["
|
||||||
|
"]"
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
] @punctuation.bracket
|
||||||
|
|
||||||
|
;; Variables
|
||||||
|
|
||||||
|
(identifier) @variable
|
||||||
|
|
||||||
|
;; Constants
|
||||||
|
|
||||||
|
(vararg_expression) @constant
|
||||||
|
|
||||||
|
((identifier) @constant
|
||||||
|
(#lua-match? @constant "^[A-Z][A-Z_0-9]*$"))
|
||||||
|
|
||||||
|
;; Tables
|
||||||
|
|
||||||
|
(field name: (identifier) @field)
|
||||||
|
|
||||||
|
(dot_index_expression field: (identifier) @field)
|
||||||
|
|
||||||
|
(table_constructor
|
||||||
|
[
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
] @constructor)
|
||||||
|
|
||||||
|
;; Functions
|
||||||
|
|
||||||
|
(parameters (identifier) @parameter)
|
||||||
|
|
||||||
|
(function_call name: (identifier) @function)
|
||||||
|
(function_declaration name: (identifier) @function)
|
||||||
|
|
||||||
|
(function_call name: (dot_index_expression field: (identifier) @function))
|
||||||
|
(function_declaration name: (dot_index_expression field: (identifier) @function))
|
||||||
|
|
||||||
|
(method_index_expression method: (identifier) @method)
|
||||||
|
|
||||||
|
(function_call
|
||||||
|
(identifier) @function.builtin
|
||||||
|
(#any-of? @function.builtin
|
||||||
|
;; built-in functions in Lua 5.1
|
||||||
|
"assert" "collectgarbage" "dofile" "error" "getfenv" "getmetatable" "ipairs"
|
||||||
|
"load" "loadfile" "loadstring" "module" "next" "pairs" "pcall" "print"
|
||||||
|
"rawequal" "rawget" "rawset" "require" "select" "setfenv" "setmetatable"
|
||||||
|
"tonumber" "tostring" "type" "unpack" "xpcall"))
|
||||||
|
|
||||||
|
;; Others
|
||||||
|
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
(hash_bang_line) @comment
|
||||||
|
|
||||||
|
(number) @number
|
||||||
|
|
||||||
|
(string) @string
|
||||||
|
|
||||||
|
;; Error
|
||||||
|
(ERROR) @error
|
1
ts/tree-sitter-json
Submodule
1
ts/tree-sitter-json
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 203e239408d642be83edde8988d6e7b20a19f0e8
|
1
ts/tree-sitter-lua
Submodule
1
ts/tree-sitter-lua
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 547184a6cfcc900fcac4a2a56538fa8bcdb293e6
|
Loading…
Reference in a new issue