Как создать редактор кода с Leptos, где оверлей отображает отформатированный код, обеспечивая правильное выравнивание?

Вопрос или проблема

Как мне убедиться, что <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> требует внимания к деталям, и приведенный выше подход поможет вам достичь желаемого результата. Уделите время тестированию вашего редактора на различных устройствах, чтобы убедиться в его удобстве и производительности.

Оцените материал
Добавить комментарий

Капча загружается...