Skip to content

Commit 3284f72

Browse files
authored
V0.2.19 (#27)
* hide if editor is inactive * completer on any TextEdit
1 parent d6ac53d commit 3284f72

File tree

4 files changed

+198
-156
lines changed

4 files changed

+198
-156
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "egui_code_editor"
33
authors = ["Roman Chumak <p4ymak@yandex.ru>"]
4-
version = "0.2.18"
4+
version = "0.2.19"
55
edition = "2024"
66
license = "MIT"
77
repository = "https://github.com/p4ymak/egui_code_editor"

examples/demo.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
22

33
use eframe::{self, CreationContext, egui};
4+
use egui::TextEdit;
45
use egui_code_editor::{self, CodeEditor, ColorTheme, Completer, Syntax, highlighting::Token};
56

67
const THEMES: [ColorTheme; 8] = [
@@ -141,6 +142,7 @@ fn main() -> Result<(), eframe::Error> {
141142
#[derive(Default)]
142143
struct CodeEditorDemo {
143144
code: String,
145+
text: String,
144146
theme: ColorTheme,
145147
syntax: Syntax,
146148
completer: Completer,
@@ -153,6 +155,7 @@ impl CodeEditorDemo {
153155
let rust = SYNTAXES[2];
154156
CodeEditorDemo {
155157
code: rust.example.to_string(),
158+
text: String::default(),
156159
theme: ColorTheme::GRUVBOX,
157160
syntax: rust.syntax(),
158161
completer: Completer::new_with_syntax(&rust.syntax()).with_user_words(),
@@ -221,8 +224,29 @@ impl eframe::App for CodeEditorDemo {
221224
.with_numlines_shift(self.shift)
222225
.with_numlines_only_natural(self.numlines_only_natural)
223226
.vscroll(true);
227+
224228
editor.show_with_completer(ui, &mut self.code, &mut self.completer);
225229

230+
ui.separator();
231+
ui.horizontal(|h| {
232+
h.label("Auto-complete TextEdit::singleLine");
233+
self.completer.show_on_text_widget(
234+
h,
235+
&Syntax::simple("#"),
236+
&ColorTheme::default(),
237+
|ui| {
238+
TextEdit::singleline(&mut self.text)
239+
.lock_focus(true)
240+
.show(ui)
241+
},
242+
);
243+
if h.button("add words").clicked() {
244+
for word in self.text.split_whitespace() {
245+
let word = word.replace(|c: char| !(c.is_alphanumeric() || c == '_'), "");
246+
self.completer.push_word(&word);
247+
}
248+
}
249+
});
226250
ui.separator();
227251

228252
egui::ScrollArea::both()
Lines changed: 57 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,174 +1,55 @@
1+
mod trie;
2+
13
use crate::{ColorTheme, Syntax, Token, TokenType, format_token};
24
use egui::{
3-
Event, Frame, Modifiers, Sense, Stroke, TextBuffer, text::CCursor, text_edit::TextEditOutput,
5+
Event, Frame, Modifiers, Sense, Stroke, TextBuffer, text_edit::TextEditOutput,
46
text_selection::text_cursor_state::ccursor_previous_word,
57
};
68
use trie::Trie;
79

8-
mod trie {
9-
#![allow(dead_code)]
10-
use std::{iter::Peekable, str::Chars};
11-
12-
const ROOT_CHAR: char = ' ';
13-
14-
#[derive(Debug, Clone)]
15-
pub struct Trie {
16-
root: char,
17-
is_word: bool,
18-
leaves: Vec<Trie>,
19-
}
20-
21-
impl PartialEq for Trie {
22-
fn eq(&self, other: &Self) -> bool {
23-
self.root == other.root
24-
}
25-
}
26-
impl PartialOrd for Trie {
27-
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
28-
Some(self.cmp(other))
29-
}
30-
}
31-
impl Ord for Trie {
32-
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
33-
self.root.cmp(&other.root)
34-
}
35-
}
36-
impl Eq for Trie {}
37-
38-
impl Default for Trie {
39-
fn default() -> Self {
40-
Self {
41-
root: ROOT_CHAR,
42-
is_word: false,
43-
leaves: vec![],
44-
}
45-
}
46-
}
47-
48-
impl Trie {
49-
pub fn new(root: char) -> Self {
50-
Trie {
51-
root,
52-
..Default::default()
53-
}
54-
}
55-
pub fn clear(&mut self) {
56-
self.leaves.clear();
57-
}
58-
pub fn push(&mut self, word: &str) {
59-
self.push_chars(&mut word.chars());
60-
}
61-
62-
pub fn push_chars(&mut self, word: &mut Chars) {
63-
if let Some(first) = word.next() {
64-
if let Some(leaf) = self.leaves.iter_mut().find(|l| l.root == first) {
65-
leaf.push_chars(word)
66-
} else {
67-
let mut new = Trie::new(first);
68-
new.push_chars(word);
69-
self.leaves.push(new);
70-
}
71-
} else {
72-
self.is_word = true;
73-
self.leaves.sort();
74-
self.leaves.reverse();
75-
}
76-
}
77-
78-
pub fn from_words(words: &[&str]) -> Self {
79-
let mut trie = Trie::new(ROOT_CHAR);
80-
words.iter().for_each(|w| {
81-
trie.push_chars(&mut w.chars());
82-
});
83-
trie
84-
}
85-
86-
pub fn words(&self) -> Vec<String> {
87-
let mut words = vec![];
88-
for child in self.leaves.iter() {
89-
child.words_recursive("", &mut words);
90-
}
91-
words.reverse();
92-
words
93-
}
94-
fn words_recursive(&self, prefix: &str, words: &mut Vec<String>) {
95-
let mut prefix = prefix.to_string();
96-
prefix.push(self.root);
97-
if self.is_word {
98-
words.push(prefix.clone());
99-
}
100-
for child in self.leaves.iter() {
101-
child.words_recursive(&prefix, words);
102-
}
103-
}
104-
105-
pub fn find_completions(&self, prefix: &str) -> Vec<String> {
106-
self.find_by_prefix(prefix)
107-
.map(|t| t.words())
108-
.unwrap_or_default()
109-
}
110-
pub fn find_by_prefix(&self, prefix: &str) -> Option<&Trie> {
111-
let mut found = None;
112-
let mut start = " ".to_string();
113-
start.push_str(prefix);
114-
let mut part = start.chars().peekable();
115-
self.find_recursice(&mut part, &mut found);
116-
found
117-
}
118-
fn find_recursice<'a>(&'a self, part: &mut Peekable<Chars>, found: &mut Option<&'a Trie>) {
119-
if let Some(c) = part.next()
120-
&& self.root == c
121-
{
122-
if part.peek().is_none() {
123-
*found = Some(self);
124-
}
125-
self.leaves
126-
.iter()
127-
.for_each(|l| l.find_recursice(&mut part.clone(), found))
128-
}
129-
}
10+
impl From<&Syntax> for Trie {
11+
fn from(syntax: &Syntax) -> Trie {
12+
let mut trie = Trie::default();
13+
14+
syntax.keywords.iter().for_each(|word| trie.push(word));
15+
syntax.types.iter().for_each(|word| trie.push(word));
16+
syntax.special.iter().for_each(|word| trie.push(word));
17+
if !syntax.case_sensitive {
18+
syntax
19+
.keywords
20+
.iter()
21+
.for_each(|word| trie.push(&word.to_lowercase()));
22+
syntax
23+
.types
24+
.iter()
25+
.for_each(|word| trie.push(&word.to_lowercase()));
26+
syntax
27+
.special
28+
.iter()
29+
.for_each(|word| trie.push(&word.to_lowercase()));
30+
}
31+
trie
13032
}
13133
}
132-
pub fn trie_from_syntax(syntax: &Syntax) -> Trie {
133-
let mut trie = Trie::default();
13434

135-
syntax.keywords.iter().for_each(|word| trie.push(word));
136-
syntax.types.iter().for_each(|word| trie.push(word));
137-
syntax.special.iter().for_each(|word| trie.push(word));
138-
if !syntax.case_sensitive {
139-
syntax
140-
.keywords
141-
.iter()
142-
.for_each(|word| trie.push(&word.to_lowercase()));
143-
syntax
144-
.types
145-
.iter()
146-
.for_each(|word| trie.push(&word.to_lowercase()));
147-
syntax
148-
.special
149-
.iter()
150-
.for_each(|word| trie.push(&word.to_lowercase()));
151-
}
152-
trie
153-
}
154-
155-
#[derive(Default, Debug, Clone)]
35+
#[derive(Default, Debug, Clone, PartialEq)]
36+
/// Code-completer with pop-up above CodeEditor.
37+
/// In future releases will be replaced with trait.
15638
pub struct Completer {
15739
prefix: String,
158-
cursor: CCursor,
40+
cursor: usize,
15941
ignore_cursor: Option<usize>,
16042
trie_syntax: Trie,
16143
trie_user: Option<Trie>,
16244
variant_id: usize,
16345
completions: Vec<String>,
16446
}
16547

166-
/// Completer shoud be stored somewhere in your App struct.
167-
/// In future releases will be replaced with trait.
16848
impl Completer {
49+
/// Completer shoud be stored somewhere in your App struct.
16950
pub fn new_with_syntax(syntax: &Syntax) -> Self {
17051
Completer {
171-
trie_syntax: trie_from_syntax(syntax),
52+
trie_syntax: Trie::from(syntax),
17253
..Default::default()
17354
}
17455
}
@@ -178,6 +59,9 @@ impl Completer {
17859
..self
17960
}
18061
}
62+
pub fn push_word(&mut self, word: &str) {
63+
self.trie_syntax.push(word);
64+
}
18165

18266
/// If using Completer without CodeEditor this method should be called before text-editing widget.
18367
/// Up/Down arrows for selection, Tab for completion, Esc for hiding
@@ -186,7 +70,7 @@ impl Completer {
18670
return;
18771
}
18872
if let Some(cursor) = self.ignore_cursor
189-
&& cursor == self.cursor.index
73+
&& cursor == self.cursor
19074
{
19175
return;
19276
}
@@ -206,7 +90,7 @@ impl Completer {
20690
let last = self.completions.len().saturating_sub(1);
20791
ctx.input_mut(|i| {
20892
if i.consume_key(Modifiers::NONE, egui::Key::Escape) {
209-
self.ignore_cursor = Some(self.cursor.index);
93+
self.ignore_cursor = Some(self.cursor);
21094
} else if i.consume_key(Modifiers::NONE, egui::Key::ArrowDown) {
21195
self.variant_id = if self.variant_id == last {
21296
0
@@ -238,6 +122,9 @@ impl Completer {
238122
fontsize: f32,
239123
editor_output: &mut TextEditOutput,
240124
) {
125+
if !editor_output.response.has_focus() {
126+
return;
127+
}
241128
let ctx = editor_output.response.ctx.clone();
242129
let galley = &editor_output.galley;
243130

@@ -263,15 +150,15 @@ impl Completer {
263150
// let cursor_on_screen = editor_output.response.rect.left_top()
264151
// + cursor_pos_in_galley.left_bottom().to_vec2();
265152
let word_start = ccursor_previous_word(galley.text(), cursor);
266-
if self.cursor != cursor {
267-
self.cursor = cursor;
153+
if self.cursor != cursor.index {
154+
self.cursor = cursor.index;
268155
self.prefix.clear();
269156
// self.completions.clear();
270157
self.ignore_cursor = None;
271158
self.variant_id = 0;
272159
}
273160

274-
if self.ignore_cursor.is_some_and(|c| c == self.cursor.index) {
161+
if self.ignore_cursor.is_some_and(|c| c == self.cursor) {
275162
editor_output.response.request_focus();
276163
return;
277164
} else {
@@ -341,4 +228,19 @@ impl Completer {
341228
}
342229
}
343230
}
231+
232+
/// Completer on text-editing widget, see demo for example
233+
pub fn show_on_text_widget(
234+
&mut self,
235+
ui: &mut egui::Ui,
236+
syntax: &Syntax,
237+
theme: &ColorTheme,
238+
mut widget: impl FnMut(&mut egui::Ui) -> TextEditOutput,
239+
) -> TextEditOutput {
240+
self.handle_input(ui.ctx());
241+
let fontsize = ui.text_style_height(&egui::TextStyle::Monospace);
242+
let mut output = widget(ui);
243+
self.show(syntax, theme, fontsize, &mut output);
244+
output
245+
}
344246
}

0 commit comments

Comments
 (0)