Вопрос или проблема
Как мне убедиться, что <textarea> и элемент правильно выровнены в моем редакторе кода Leptos?
Я работаю над редактором кода, где <textarea> накладывается на элемент для отображения отформатированного кода. Проблема, с которой я сталкиваюсь, заключается в том, что когда я вставляю пробелы или табуляцию в <textarea>, выравнивание с элементом становится несогласованным.
Поскольку я использую Tauri для моего приложения, есть ли какие-либо конкретные моменты, которые мне следует учитывать? Что я могу сделать, чтобы сохранить выравнивание обоих элементов, особенно при работе с пробелами или табуляцией? Любые предложения или лучшие практики для достижения этого будут очень полезны!
pub fn gen_span_class(&mut self) -> String {
let mut formatted_code = String::new();
while let Some(token) = self.next_token() {
let token_str = match token {
Token::Keyword(keyword) => format!("<span class=\"keyword\">{}</span>", keyword),
Token::Identifier(identifier) => {
format!("<span class=\"identifier\">{}</span>", identifier)
}
Token::Number(number) => format!("<span class=\"number\">{}</span>", number),
Token::Operator(op) => format!("<span class=\"operator\">{}</span>", op),
Token::Symbol(symbol) => format!("<span class=\"symbol\">{}</span>", symbol),
Token::Macro(mac) => format!("<span class=\"macro\">{}</span>", mac),
Token::Function(func) => format!("<span class=\"function\">{}</span>", func),
Token::String(s) => format!("<span class=\"string\">{}</span>", s),
Token::Result(r) => format!("<span class=\"type\">{}</span>", r),
Token::Bool(b) => format!("<span class=\"type\">{}</span>", b),
Token::Option(o) => format!("<span class=\"type\">{}</span>", o),
Token::Unknown(ch) => ch.to_string(),
Token::Whitespace => " ".to_string(),
Token::Breakline => "<br>".to_string(),
Token::Borrow => "<span class=\"borrow\">&</span>".to_string()
};
formatted_code.push_str(&token_str);
}
formatted_code
}
use ev::{Event, KeyboardEvent};
use leptos::*;
use wasm_bindgen::prelude::*;
use web_sys::{HtmlElement, HtmlTextAreaElement};
use crate::components::lexer::Lexer;
#[component]
pub fn Code() -> impl IntoView {
let (code, write_code) = create_signal(String::new());
let tab = move |ev: KeyboardEvent| {
if ev.key() == "Tab" {
ev.prevent_default();
if let Some(target) = ev.target().and_then(|t| t.dyn_into::<HtmlTextAreaElement>().ok()) {
let selection_start = target.selection_start().unwrap();
let selection_end = target.selection_end().unwrap();
let value = target.value();
let new_value = format!("{} {}", &value[..selection_start.unwrap() as usize], &value[selection_end.unwrap() as usize..]);
target.set_value(&new_value);
target.set_selection_start(Some(selection_start.unwrap() + 3)).unwrap();
target.set_selection_end(Some(selection_start.unwrap() + 3)).unwrap();
}
}
};
let on_input = move |ev: Event| {
if let Some(target) = ev.target().and_then(|t| t.dyn_into::<HtmlTextAreaElement>().ok()) {
let code_value = target.value();
write_code.set(code_value);
}
};
let highlighted_code = move || {
let mut lexer = Lexer::new(&code.get());
lexer.gen_span_class()
};
let sync_scroll = move |ev: Event| {
if let Some(target) = ev.target().and_then(|t| t.dyn_into::<HtmlTextAreaElement>().ok()) {
if let Some(display) = document().get_element_by_id("code-display") {
let display = display.unchecked_into::<HtmlElement>();
display.set_scroll_top(target.scroll_top());
display.set_scroll_left(target.scroll_left())
}
}
};
view! {
<textarea
on:keydown=tab
on:input=on_input
on:scroll=sync_scroll
id="editor-text-area"
placeholder="Введите ваш код здесь..."
class="editor-textarea"
/>
<pre id="code-display" class="code-display">
<code inner_html=highlighted_code />
</pre>
}
}
.keyword, .operator, .symbol, .borrow, .macro, .function, .string {
font-weight: bold;
}
.keyword, .operator, .symbol, .borrow {
color: #f4f025;
font-style: italic;
}
.macro, .function {
color: #66ff00;
}
.string {
color: orange;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: rgba(15, 23, 42, 0.6);
font-family: 'Fira Code', monospace;
font-size: 16px;
color: white;
margin: 0;
padding: 0;
}
.editor-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.editor-textarea,
.code-display {
font-family: 'Fira Code', monospace;
font-size: 16px;
line-height: 24px;
padding: 10px;
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
}
.editor-textarea {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
color: red;
background-color: transparent;
border: none;
outline: none;
caret-color: white;
z-index: 2;
resize: none;
overflow: hidden;
}
.code-display {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
color: white;
z-index: 1;
pointer-events: none;
}
Ответ или решение
Создание редактора кода с использованием Leptos, в котором <textarea>
накладывается на <pre>
для отображения отформатированного кода, представляет собой интересную задачу, связную с обеспечением правильного выравнивания элементов. В этой статье мы рассмотрим, как добиться нужного результата, а также учтем особенности использования Tauri при разработке вашего приложения.
I. Общая структура проекта
Начнем с основной структуры компонента. Мы будем использовать библиотеку Leptos для создания интерфейса и управлять событиями для обработки ввода и прокрутки полей. Компонент будет содержать два элемента: textarea
для ввода текста и pre
для отображения синтаксически окрашенного кода.
II. Обработка событий
При создании редактора следует предусмотреть обработку нескольких ключевых событий:
input
— для отслеживания изменения текста вtextarea
.keydown
— для обработки нажатия клавишиTab
.scroll
— для синхронизации прокрутки между двумя элементами.
Вот пример кода для основного компонента:
use ev::{Event, KeyboardEvent};
use leptos::*;
use wasm_bindgen::prelude::*;
use web_sys::{HtmlElement, HtmlTextAreaElement};
use crate::components::lexer::Lexer;
#[component]
pub fn Code() -> impl IntoView {
let (code, write_code) = create_signal(String::new());
let tab = move |ev: KeyboardEvent| {
if ev.key() == "Tab" {
ev.prevent_default();
if let Some(target) = ev.target().and_then(|t| t.dyn_into::<HtmlTextAreaElement>().ok()) {
let selection_start = target.selection_start().unwrap();
let new_value = format!("{} {}", &target.value()[..selection_start as usize], &target.value()[target.selection_end().unwrap() as usize..]);
target.set_value(&new_value);
target.set_selection_start(Some(selection_start + 4)).unwrap();
target.set_selection_end(Some(selection_start + 4)).unwrap();
}
}
};
let on_input = move |ev: Event| {
if let Some(target) = ev.target().and_then(|t| t.dyn_into::<HtmlTextAreaElement>().ok()) {
write_code.set(target.value());
}
};
let highlighted_code = move || {
let mut lexer = Lexer::new(&code.get());
lexer.gen_span_class()
};
let sync_scroll = move |ev: Event| {
if let Some(target) = ev.target().and_then(|t| t.dyn_into::<HtmlTextAreaElement>().ok()) {
if let Some(display) = document().get_element_by_id("code-display") {
let display = display.unchecked_into::<HtmlElement>();
display.set_scroll_top(target.scroll_top());
display.set_scroll_left(target.scroll_left());
}
}
};
view! {
<textarea
on:keydown=tab
on:input=on_input
on:scroll=sync_scroll
id="editor-text-area"
placeholder="Type your code here..."
class="editor-textarea"
/>
<pre id="code-display" class="code-display">
<code inner_html=highlighted_code />
</pre>
}
}
III. Стилизация и выравнивание
Чтобы обеспечить правильное выравнивание между текстовым полем и отображаемым кодом, используйте единый шрифт (например, 'Fira Code'
), а также убедитесь, что параметры отступов и межстрочного интервала совпадают у обоих элементов.
Вот пример CSS для вашего редактора:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: rgba(15, 23, 42, 0.6);
font-family: 'Fira Code', monospace;
font-size: 16px;
color: white;
}
.editor-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.editor-textarea,
.code-display {
font-family: 'Fira Code', monospace;
font-size: 16px;
line-height: 24px;
padding: 10px;
margin: 0;
white-space: pre;
overflow-wrap: break-word;
overflow: auto;
}
.editor-textarea {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: transparent;
border: none;
outline: none;
caret-color: white;
z-index: 2;
resize: none;
}
.code-display {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
pointer-events: none; /* отключаем события для этого элемента */
}
IV. Особенности Tauri
При работе с Tauri следует помнить, что ваша веб-страница будет работать в контексте нативного приложения. Убедитесь, что у вас настроены все зависимости для работы с WebAssembly и Leptos, так как производительность может быть критична для редакторов кода. Также обратите внимание на правильную работу вашего приложения в зависимости от разрешения экрана.
Заключение
Создание редактора кода с использованием Leptos — это интересная задача, в которой важно всё: от обработки событий до визуального оформления. Правильное выравнивание <textarea>
и <pre>
требует внимания к деталям, и приведенный выше подход поможет вам достичь желаемого результата. Уделите время тестированию вашего редактора на различных устройствах, чтобы убедиться в его удобстве и производительности.