2025-07-05 14:42:45 -04:00
|
|
|
use crate::app::TextEditor;
|
|
|
|
|
use eframe::egui;
|
2025-07-21 20:28:09 -04:00
|
|
|
use egui_extras::syntax_highlighting::{self, CodeTheme};
|
2025-07-05 14:42:45 -04:00
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
use super::find_highlight;
|
|
|
|
|
|
2025-07-21 20:28:09 -04:00
|
|
|
fn get_language_from_extension(file_path: Option<&std::path::Path>) -> String {
|
|
|
|
|
if let Some(path) = file_path {
|
|
|
|
|
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
|
|
|
|
|
match extension.to_lowercase().as_str() {
|
|
|
|
|
"rs" => "rs".to_string(),
|
|
|
|
|
"py" => "py".to_string(),
|
|
|
|
|
"js" => "js".to_string(),
|
|
|
|
|
"ts" => "ts".to_string(),
|
|
|
|
|
"tsx" => "tsx".to_string(),
|
|
|
|
|
"jsx" => "jsx".to_string(),
|
|
|
|
|
"c" => "c".to_string(),
|
|
|
|
|
"cpp" | "cc" | "cxx" => "cpp".to_string(),
|
|
|
|
|
"h" | "hpp" => "cpp".to_string(),
|
|
|
|
|
"java" => "java".to_string(),
|
|
|
|
|
"go" => "go".to_string(),
|
|
|
|
|
"php" => "php".to_string(),
|
|
|
|
|
"rb" => "rb".to_string(),
|
|
|
|
|
"cs" => "cs".to_string(),
|
|
|
|
|
"swift" => "swift".to_string(),
|
|
|
|
|
"kt" => "kt".to_string(),
|
|
|
|
|
"scala" => "scala".to_string(),
|
|
|
|
|
"sh" | "bash" | "zsh" | "fish" => "sh".to_string(),
|
|
|
|
|
"html" | "htm" => "html".to_string(),
|
|
|
|
|
"xml" => "xml".to_string(),
|
|
|
|
|
"css" => "css".to_string(),
|
|
|
|
|
"scss" | "sass" => "scss".to_string(),
|
|
|
|
|
"json" => "json".to_string(),
|
|
|
|
|
"yaml" | "yml" => "yaml".to_string(),
|
|
|
|
|
"toml" => "toml".to_string(),
|
|
|
|
|
"md" | "markdown" => "md".to_string(),
|
|
|
|
|
"sql" => "sql".to_string(),
|
|
|
|
|
"lua" => "lua".to_string(),
|
|
|
|
|
"vim" => "vim".to_string(),
|
|
|
|
|
"dockerfile" => "dockerfile".to_string(),
|
|
|
|
|
"makefile" => "makefile".to_string(),
|
|
|
|
|
_ => "txt".to_string(),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Check filename for special cases
|
|
|
|
|
if let Some(filename) = path.file_name().and_then(|name| name.to_str()) {
|
|
|
|
|
match filename.to_lowercase().as_str() {
|
|
|
|
|
"dockerfile" => "dockerfile".to_string(),
|
|
|
|
|
"makefile" => "makefile".to_string(),
|
|
|
|
|
"cargo.toml" | "pyproject.toml" => "toml".to_string(),
|
|
|
|
|
"package.json" => "json".to_string(),
|
|
|
|
|
_ => "txt".to_string(),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
"txt".to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
"txt".to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create_code_theme_from_visuals(visuals: &egui::Visuals, font_size: f32) -> CodeTheme {
|
|
|
|
|
// For now, just use the appropriate base theme (dark/light)
|
|
|
|
|
// The base themes should automatically work well with the system colors
|
|
|
|
|
// since egui's syntax highlighting respects the overall UI theme
|
|
|
|
|
if visuals.dark_mode {
|
|
|
|
|
CodeTheme::dark(font_size)
|
|
|
|
|
} else {
|
|
|
|
|
CodeTheme::light(font_size)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) -> egui::Response {
|
|
|
|
|
let _current_match_position = app.get_current_match_position();
|
2025-07-05 14:42:45 -04:00
|
|
|
let show_find = app.show_find;
|
2025-07-15 00:42:01 -04:00
|
|
|
let _prev_show_find = app.prev_show_find;
|
2025-07-05 14:42:45 -04:00
|
|
|
let show_preferences = app.show_preferences;
|
|
|
|
|
let show_about = app.show_about;
|
|
|
|
|
let show_shortcuts = app.show_shortcuts;
|
|
|
|
|
let word_wrap = app.word_wrap;
|
|
|
|
|
let font_size = app.font_size;
|
|
|
|
|
|
|
|
|
|
let reset_zoom_key = egui::Id::new("editor_reset_zoom");
|
2025-07-15 00:42:01 -04:00
|
|
|
let should_reset_zoom = ui
|
|
|
|
|
.ctx()
|
|
|
|
|
.memory_mut(|mem| mem.data.get_temp::<bool>(reset_zoom_key).unwrap_or(false));
|
|
|
|
|
|
2025-07-05 14:42:45 -04:00
|
|
|
if should_reset_zoom {
|
|
|
|
|
app.zoom_factor = 1.0;
|
|
|
|
|
ui.ctx().set_zoom_factor(1.0);
|
|
|
|
|
ui.ctx().memory_mut(|mem| {
|
|
|
|
|
mem.data.insert_temp(reset_zoom_key, false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
let estimated_width = if !word_wrap {
|
|
|
|
|
app.calculate_content_based_width(ui)
|
|
|
|
|
} else {
|
|
|
|
|
0.0
|
|
|
|
|
};
|
2025-07-05 14:42:45 -04:00
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
let find_data = if show_find && !app.find_matches.is_empty() {
|
|
|
|
|
app.get_active_tab().map(|tab| {
|
|
|
|
|
(
|
2025-07-16 17:20:09 -04:00
|
|
|
tab.content.to_owned(),
|
|
|
|
|
app.find_matches.to_owned(),
|
2025-07-16 13:27:31 -04:00
|
|
|
app.current_match_index,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
let Some(active_tab) = app.get_active_tab_mut() else {
|
|
|
|
|
return ui.label("No file open, how did you get here?");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let bg_color = ui.visuals().extreme_bg_color;
|
|
|
|
|
let editor_rect = ui.available_rect_before_wrap();
|
|
|
|
|
ui.painter().rect_filled(editor_rect, 0.0, bg_color);
|
2025-07-05 14:42:45 -04:00
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
if let Some((content, matches, current_match_index)) = &find_data {
|
|
|
|
|
let font_id = ui
|
|
|
|
|
.style()
|
|
|
|
|
.text_styles
|
|
|
|
|
.get(&egui::TextStyle::Monospace)
|
|
|
|
|
.unwrap_or(&egui::FontId::monospace(font_size))
|
2025-07-16 17:20:09 -04:00
|
|
|
.to_owned();
|
2025-07-16 13:27:31 -04:00
|
|
|
|
|
|
|
|
let desired_width = if word_wrap {
|
|
|
|
|
ui.available_width()
|
|
|
|
|
} else {
|
|
|
|
|
f32::INFINITY
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let temp_galley = ui.fonts(|fonts| {
|
|
|
|
|
fonts.layout(
|
2025-07-16 17:20:09 -04:00
|
|
|
content.to_owned(),
|
|
|
|
|
font_id.to_owned(),
|
2025-07-16 13:27:31 -04:00
|
|
|
ui.visuals().text_color(),
|
|
|
|
|
desired_width,
|
|
|
|
|
)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let text_area_left = editor_rect.left() + 4.0;
|
|
|
|
|
let text_area_top = editor_rect.top() + 2.0;
|
|
|
|
|
|
|
|
|
|
find_highlight::draw_find_highlights(
|
|
|
|
|
ui,
|
|
|
|
|
content,
|
|
|
|
|
matches,
|
|
|
|
|
*current_match_index,
|
|
|
|
|
&temp_galley,
|
|
|
|
|
text_area_left,
|
|
|
|
|
text_area_top,
|
|
|
|
|
font_size,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
let desired_width = if word_wrap {
|
|
|
|
|
ui.available_width()
|
|
|
|
|
} else {
|
|
|
|
|
f32::INFINITY
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-21 20:28:09 -04:00
|
|
|
// Determine the language for syntax highlighting
|
|
|
|
|
let language = get_language_from_extension(active_tab.file_path.as_deref());
|
|
|
|
|
|
|
|
|
|
// Create a code theme based on the current system theme visuals
|
|
|
|
|
let theme = create_code_theme_from_visuals(ui.visuals(), font_size);
|
|
|
|
|
|
|
|
|
|
let mut layouter = |ui: &egui::Ui, string: &dyn egui::TextBuffer, wrap_width: f32| {
|
|
|
|
|
let text = string.as_str();
|
|
|
|
|
let mut layout_job = if language == "txt" {
|
|
|
|
|
syntax_highlighting::highlight(ui.ctx(), &ui.style().clone(), &theme, text, "")
|
|
|
|
|
} else {
|
|
|
|
|
syntax_highlighting::highlight(ui.ctx(), &ui.style().clone(), &theme, text, &language)
|
|
|
|
|
};
|
|
|
|
|
layout_job.wrap.max_width = wrap_width;
|
|
|
|
|
ui.fonts(|f| f.layout_job(layout_job))
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
let text_edit = egui::TextEdit::multiline(&mut active_tab.content)
|
|
|
|
|
.frame(false)
|
|
|
|
|
.font(egui::TextStyle::Monospace)
|
|
|
|
|
.code_editor()
|
|
|
|
|
.desired_width(desired_width)
|
|
|
|
|
.desired_rows(0)
|
2025-07-16 13:27:31 -04:00
|
|
|
.lock_focus(!show_find)
|
2025-07-15 00:42:01 -04:00
|
|
|
.cursor_at_end(false)
|
2025-07-21 20:28:09 -04:00
|
|
|
.layouter(&mut layouter)
|
2025-07-15 00:42:01 -04:00
|
|
|
.id(egui::Id::new("main_text_editor"));
|
|
|
|
|
|
|
|
|
|
let output = if word_wrap {
|
|
|
|
|
text_edit.show(ui)
|
|
|
|
|
} else {
|
|
|
|
|
egui::ScrollArea::horizontal()
|
|
|
|
|
.auto_shrink([false; 2])
|
|
|
|
|
.show(ui, |ui| {
|
|
|
|
|
ui.allocate_ui_with_layout(
|
|
|
|
|
egui::Vec2::new(estimated_width, ui.available_height()),
|
|
|
|
|
egui::Layout::left_to_right(egui::Align::TOP),
|
|
|
|
|
|ui| text_edit.show(ui),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.inner
|
|
|
|
|
.inner
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let content_changed = output.response.changed();
|
|
|
|
|
let content_for_processing = if content_changed {
|
|
|
|
|
active_tab.update_modified_state();
|
2025-07-16 17:20:09 -04:00
|
|
|
Some(active_tab.content.to_owned())
|
2025-07-15 00:42:01 -04:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
if content_changed && app.show_find && !app.find_query.is_empty() {
|
|
|
|
|
app.update_find_matches();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
let current_cursor_pos = output
|
|
|
|
|
.state
|
|
|
|
|
.cursor
|
|
|
|
|
.char_range()
|
|
|
|
|
.map(|range| range.primary.index);
|
|
|
|
|
|
|
|
|
|
if let Some(content) = content_for_processing {
|
2025-07-16 17:20:09 -04:00
|
|
|
let previous_content = app.previous_content.to_owned();
|
2025-07-15 00:42:01 -04:00
|
|
|
let previous_cursor_pos = app.previous_cursor_char_index;
|
|
|
|
|
|
|
|
|
|
if !previous_content.is_empty() {
|
|
|
|
|
if let (Some(prev_cursor_pos), Some(curr_cursor_pos)) =
|
|
|
|
|
(previous_cursor_pos, current_cursor_pos)
|
|
|
|
|
{
|
|
|
|
|
app.process_incremental_change(
|
|
|
|
|
&previous_content,
|
|
|
|
|
&content,
|
|
|
|
|
prev_cursor_pos,
|
|
|
|
|
curr_cursor_pos,
|
2025-07-05 14:42:45 -04:00
|
|
|
ui,
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-16 13:27:31 -04:00
|
|
|
} else {
|
|
|
|
|
app.process_text_for_rendering(&content, ui);
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
|
|
|
|
|
2025-07-16 17:20:09 -04:00
|
|
|
app.previous_content = content.to_owned();
|
2025-07-15 00:42:01 -04:00
|
|
|
app.previous_cursor_char_index = current_cursor_pos;
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
|
|
|
|
|
2025-07-16 17:20:09 -04:00
|
|
|
if app.font_settings_changed || app.text_needs_processing {
|
2025-07-15 09:53:45 -04:00
|
|
|
if let Some(active_tab) = app.get_active_tab() {
|
2025-07-16 17:20:09 -04:00
|
|
|
let content = active_tab.content.to_owned();
|
2025-07-16 13:27:31 -04:00
|
|
|
app.process_text_for_rendering(&content, ui);
|
|
|
|
|
}
|
2025-07-16 17:20:09 -04:00
|
|
|
app.font_settings_changed = false;
|
2025-07-16 13:27:31 -04:00
|
|
|
app.text_needs_processing = false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
if !word_wrap {
|
|
|
|
|
if let Some(cursor_pos) = current_cursor_pos {
|
|
|
|
|
let cursor_moved = Some(cursor_pos) != app.previous_cursor_position;
|
|
|
|
|
let text_changed = output.response.changed();
|
|
|
|
|
|
|
|
|
|
if cursor_moved || text_changed {
|
|
|
|
|
if let Some(active_tab) = app.get_active_tab() {
|
|
|
|
|
let content = &active_tab.content;
|
|
|
|
|
let cursor_line = content
|
|
|
|
|
.char_indices()
|
|
|
|
|
.take_while(|(byte_pos, _)| *byte_pos < cursor_pos)
|
|
|
|
|
.filter(|(_, ch)| *ch == '\n')
|
|
|
|
|
.count();
|
|
|
|
|
|
|
|
|
|
let font_id = ui
|
|
|
|
|
.style()
|
|
|
|
|
.text_styles
|
|
|
|
|
.get(&egui::TextStyle::Monospace)
|
|
|
|
|
.unwrap_or(&egui::FontId::monospace(font_size))
|
2025-07-16 17:20:09 -04:00
|
|
|
.to_owned();
|
2025-07-15 00:42:01 -04:00
|
|
|
let line_height = ui.fonts(|fonts| fonts.row_height(&font_id));
|
|
|
|
|
|
|
|
|
|
let y_pos = output.response.rect.top() + (cursor_line as f32 * line_height);
|
|
|
|
|
let cursor_rect = egui::Rect::from_min_size(
|
|
|
|
|
egui::pos2(output.response.rect.left(), y_pos),
|
|
|
|
|
egui::vec2(2.0, line_height),
|
|
|
|
|
);
|
2025-07-05 14:42:45 -04:00
|
|
|
|
|
|
|
|
let visible_area = ui.clip_rect();
|
2025-07-15 00:42:01 -04:00
|
|
|
if !visible_area.intersects(cursor_rect) {
|
|
|
|
|
ui.scroll_to_rect(cursor_rect, Some(egui::Align::Center));
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
2025-07-15 00:42:01 -04:00
|
|
|
app.previous_cursor_position = Some(cursor_pos);
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-15 00:42:01 -04:00
|
|
|
|
|
|
|
|
if !output.response.has_focus()
|
|
|
|
|
&& !show_preferences
|
|
|
|
|
&& !show_about
|
|
|
|
|
&& !show_shortcuts
|
|
|
|
|
&& !show_find
|
|
|
|
|
{
|
|
|
|
|
output.response.request_focus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
output.response
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|