Skip to content

Commit

Permalink
Adds navigation for code editor page
Browse files Browse the repository at this point in the history
  • Loading branch information
bitbrain-za committed Nov 9, 2023
1 parent 82e1c8f commit 18837e8
Show file tree
Hide file tree
Showing 7 changed files with 431 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "challenge_frontend"
version = "1.0.1"
version = "2.0.0"
edition = "2021"
rust-version = "1.71"
authors = ["Philip Barlow"]
Expand Down
239 changes: 239 additions & 0 deletions src/code_editor/editor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
use egui::{text_edit::CCursorRange, *};

#[derive(serde::Deserialize, serde::Serialize)]
pub struct CodeEditor {
code: String,
highlight_editor: bool,
show_rendered: bool,
}

impl PartialEq for CodeEditor {
fn eq(&self, other: &Self) -> bool {
(&self.code, self.highlight_editor, self.show_rendered)
== (&other.code, other.highlight_editor, other.show_rendered)
}
}

impl Default for CodeEditor {
fn default() -> Self {
Self {
code: DEFAULT_CODE.trim().to_owned(),
highlight_editor: true,
show_rendered: true,
}
}
}

impl CodeEditor {
pub fn panels(&mut self, ctx: &egui::Context) {
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {

Check warning on line 29 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check wasm32

unused variable: `ui`

Check warning on line 29 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check

unused variable: `ui`

Check warning on line 29 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused variable: `ui`

Check failure on line 29 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused variable: `ui`
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);

Check warning on line 30 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check wasm32

unused variable: `layout`

Check warning on line 30 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check

unused variable: `layout`

Check warning on line 30 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused variable: `layout`

Check failure on line 30 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused variable: `layout`
});

egui::CentralPanel::default().show(ctx, |ui| {
self.ui(ui);
});
}

pub fn ui(&mut self, ui: &mut egui::Ui) {
egui::Grid::new("controls").show(ui, |ui| {
let _ = ui.button("Hotkeys").on_hover_ui(nested_hotkeys_ui);
ui.checkbox(&mut self.show_rendered, "Show rendered");
ui.checkbox(&mut self.highlight_editor, "Highlight editor");
egui::reset_button(ui, self);
ui.end_row();
});
ui.separator();

if self.show_rendered {
ui.columns(2, |columns| {
ScrollArea::vertical()
.id_source("source")
.show(&mut columns[0], |ui| self.editor_ui(ui));
ScrollArea::vertical()
.id_source("rendered")
.show(&mut columns[1], |ui| {});

Check warning on line 55 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check wasm32

unused variable: `ui`

Check warning on line 55 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check

unused variable: `ui`

Check warning on line 55 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused variable: `ui`

Check failure on line 55 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused variable: `ui`
});
} else {
ScrollArea::vertical()
.id_source("source")
.show(ui, |ui| self.editor_ui(ui));
}
}

fn editor_ui(&mut self, ui: &mut egui::Ui) {}

Check warning on line 64 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check wasm32

unused variable: `ui`

Check warning on line 64 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check

unused variable: `ui`

Check warning on line 64 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused variable: `ui`

Check failure on line 64 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused variable: `ui`
}

pub const SHORTCUT_BOLD: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::B);
pub const SHORTCUT_CODE: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::N);
pub const SHORTCUT_ITALICS: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::I);
pub const SHORTCUT_SUBSCRIPT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::L);
pub const SHORTCUT_SUPERSCRIPT: KeyboardShortcut =
KeyboardShortcut::new(Modifiers::COMMAND, Key::Y);
pub const SHORTCUT_STRIKETHROUGH: KeyboardShortcut =
KeyboardShortcut::new(Modifiers::CTRL.plus(Modifiers::SHIFT), Key::Q);
pub const SHORTCUT_UNDERLINE: KeyboardShortcut =
KeyboardShortcut::new(Modifiers::CTRL.plus(Modifiers::SHIFT), Key::W);
pub const SHORTCUT_INDENT: KeyboardShortcut =
KeyboardShortcut::new(Modifiers::CTRL.plus(Modifiers::SHIFT), Key::E);

fn nested_hotkeys_ui(ui: &mut egui::Ui) {
egui::Grid::new("shortcuts").striped(true).show(ui, |ui| {
let mut label = |shortcut, what| {
ui.label(what);
ui.weak(ui.ctx().format_shortcut(&shortcut));
ui.end_row();
};

label(SHORTCUT_BOLD, "*bold*");
label(SHORTCUT_CODE, "`code`");
label(SHORTCUT_ITALICS, "/italics/");
label(SHORTCUT_SUBSCRIPT, "$subscript$");
label(SHORTCUT_SUPERSCRIPT, "^superscript^");
label(SHORTCUT_STRIKETHROUGH, "~strikethrough~");
label(SHORTCUT_UNDERLINE, "_underline_");
label(SHORTCUT_INDENT, "two spaces"); // Placeholder for tab indent
});
}

fn shortcuts(ui: &Ui, code: &mut dyn TextBuffer, ccursor_range: &mut CCursorRange) -> bool {

Check warning on line 99 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check wasm32

function `shortcuts` is never used

Check warning on line 99 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check

function `shortcuts` is never used

Check warning on line 99 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Test Suite

function `shortcuts` is never used

Check failure on line 99 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Clippy

function `shortcuts` is never used
let mut any_change = false;

if ui.input_mut(|i| i.consume_shortcut(&SHORTCUT_INDENT)) {
// This is a placeholder till we can indent the active line
any_change = true;
let [primary, _secondary] = ccursor_range.sorted();

let advance = code.insert_text(" ", primary.index);
ccursor_range.primary.index += advance;
ccursor_range.secondary.index += advance;
}

for (shortcut, surrounding) in [
(SHORTCUT_BOLD, "*"),
(SHORTCUT_CODE, "`"),
(SHORTCUT_ITALICS, "/"),
(SHORTCUT_SUBSCRIPT, "$"),
(SHORTCUT_SUPERSCRIPT, "^"),
(SHORTCUT_STRIKETHROUGH, "~"),
(SHORTCUT_UNDERLINE, "_"),
] {
if ui.input_mut(|i| i.consume_shortcut(&shortcut)) {
any_change = true;
toggle_surrounding(code, ccursor_range, surrounding);
};
}

any_change
}

/// E.g. toggle *strong* with `toggle_surrounding(&mut text, &mut cursor, "*")`
fn toggle_surrounding(

Check warning on line 131 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check wasm32

function `toggle_surrounding` is never used

Check warning on line 131 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Check

function `toggle_surrounding` is never used

Check warning on line 131 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Test Suite

function `toggle_surrounding` is never used

Check failure on line 131 in src/code_editor/editor.rs

View workflow job for this annotation

GitHub Actions / Clippy

function `toggle_surrounding` is never used
code: &mut dyn TextBuffer,
ccursor_range: &mut CCursorRange,
surrounding: &str,
) {
let [primary, secondary] = ccursor_range.sorted();

let surrounding_ccount = surrounding.chars().count();

let prefix_crange = primary.index.saturating_sub(surrounding_ccount)..primary.index;
let suffix_crange = secondary.index..secondary.index.saturating_add(surrounding_ccount);
let already_surrounded = code.char_range(prefix_crange.clone()) == surrounding
&& code.char_range(suffix_crange.clone()) == surrounding;

if already_surrounded {
code.delete_char_range(suffix_crange);
code.delete_char_range(prefix_crange);
ccursor_range.primary.index -= surrounding_ccount;
ccursor_range.secondary.index -= surrounding_ccount;
} else {
code.insert_text(surrounding, secondary.index);
let advance = code.insert_text(surrounding, primary.index);

ccursor_range.primary.index += advance;
ccursor_range.secondary.index += advance;
}
}

// ----------------------------------------------------------------------------

const DEFAULT_CODE: &str = r#"
# EasyMark
EasyMark is a markup language, designed for extreme simplicity.
```
WARNING: EasyMark is still an evolving specification,
and is also missing some features.
```
----------------
# At a glance
- inline text:
- normal, `code`, *strong*, ~strikethrough~, _underline_, /italics/, ^raised^, $small$
- `\` escapes the next character
- [hyperlink](https://github.com/emilk/egui)
- Embedded URL: <https://github.com/emilk/egui>
- `# ` header
- `---` separator (horizontal line)
- `> ` quote
- `- ` bullet list
- `1. ` numbered list
- \`\`\` code fence
- a^2^ + b^2^ = c^2^
- $Remember to read the small print$
# Design
> /"Why do what everyone else is doing, when everyone else is already doing it?"
> \- Emil
Goals:
1. easy to parse
2. easy to learn
3. similar to markdown
[The reference parser](https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/easy_mark/easy_mark_parser.rs) is \~250 lines of code, using only the Rust standard library. The parser uses no look-ahead or recursion.
There is never more than one way to accomplish the same thing, and each special character is only used for one thing. For instance `*` is used for *strong* and `-` is used for bullet lists. There is no alternative way to specify the *strong* style or getting a bullet list.
Similarity to markdown is kept when possible, but with much less ambiguity and some improvements (like _underlining_).
# Details
All style changes are single characters, so it is `*strong*`, NOT `**strong**`. Style is reset by a matching character, or at the end of the line.
Style change characters and escapes (`\`) work everywhere except for in inline code, code blocks and in URLs.
You can mix styles. For instance: /italics _underline_/ and *strong `code`*.
You can use styles on URLs: ~my webpage is at <http://www.example.com>~.
Newlines are preserved. If you want to continue text on the same line, just do so. Alternatively, escape the newline by ending the line with a backslash (`\`). \
Escaping the newline effectively ignores it.
The style characters are chosen to be similar to what they are representing:
`_` = _underline_
`~` = ~strikethrough~ (`-` is used for bullet points)
`/` = /italics/
`*` = *strong*
`$` = $small$
`^` = ^raised^
# TODO
- Sub-headers (`## h2`, `### h3` etc)
- Hotkey Editor
- International keyboard algorithm for non-letter keys
- ALT+SHIFT+Num1 is not a functioning hotkey
- Tab Indent Increment/Decrement CTRL+], CTRL+[
- Images
- we want to be able to optionally specify size (width and\/or height)
- centering of images is very desirable
- captioning (image with a text underneath it)
- `![caption=My image][width=200][center](url)` ?
- Nicer URL:s
- `<url>` and `[url](url)` do the same thing yet look completely different.
- let's keep similarity with images
- Tables
- Inspiration: <https://mycorrhiza.lesarbr.es/page/mycomarkup>
"#;
3 changes: 3 additions & 0 deletions src/code_editor/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod editor;

pub use editor::CodeEditor;
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#![warn(clippy::all, rust_2018_idioms)]

mod wrap_app;
pub use wrap_app::CodeChallengeApp;
pub use wrap_app::WrapApp;

pub mod apps;
pub mod code_editor;
pub mod components;
pub mod helpers;
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fn main() -> eframe::Result<()> {
eframe::run_native(
"my app",
native_options,
Box::new(|cc| Box::new(challenge_frontend::CodeChallengeApp::new(cc))),
Box::new(|cc| Box::new(challenge_frontend::WrapApp::new(cc))),
)
}

Expand All @@ -36,7 +36,7 @@ fn main() {
.start(
"the_canvas_id", // hardcode it
web_options,
Box::new(|cc| Box::new(challenge_frontend::CodeChallengeApp::new(cc))),
Box::new(|cc| Box::new(challenge_frontend::WrapApp::new(cc))),
)
.await
.expect("failed to start eframe");
Expand Down
Loading

0 comments on commit 18837e8

Please sign in to comment.