use utf-16 for everything because of javascript
This commit is contained in:
parent
7e95a338de
commit
090f3bcb23
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -1084,6 +1084,12 @@ dependencies = [
|
|||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@ -1246,4 +1252,5 @@ dependencies = [
|
|||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
@ -14,3 +14,4 @@ tokio = { version = "1.25.0", features = ["full"] }
|
|||||||
tower-http = { version = "0.3.5", features = ["trace"] }
|
tower-http = { version = "0.3.5", features = ["trace"] }
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||||
|
unicode-segmentation = "1.10.1"
|
||||||
|
@ -16,6 +16,9 @@ let ws;
|
|||||||
const uuid = self.crypto.randomUUID();
|
const uuid = self.crypto.randomUUID();
|
||||||
const wsUrl = "ws://localhost:3000/ws";
|
const wsUrl = "ws://localhost:3000/ws";
|
||||||
|
|
||||||
|
const textEncoder = new TextEncoder();
|
||||||
|
const textDecoder = new TextDecoder();
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
area = document.querySelector(`#${areaId}`);
|
area = document.querySelector(`#${areaId}`);
|
||||||
ws = new WebSocket(wsUrl);
|
ws = new WebSocket(wsUrl);
|
||||||
@ -26,7 +29,7 @@ function setup() {
|
|||||||
|
|
||||||
function setupUi() {
|
function setupUi() {
|
||||||
document.addEventListener("selectionchange", onSelectionChange, false);
|
document.addEventListener("selectionchange", onSelectionChange, false);
|
||||||
area.addEventListener("input", onInput, false);
|
area.addEventListener("beforeinput", onInput, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupWs() {
|
function setupWs() {
|
||||||
@ -40,6 +43,7 @@ function setupWs() {
|
|||||||
ws.onmessage = function (e) {
|
ws.onmessage = function (e) {
|
||||||
let payload = JSON.parse(e.data);
|
let payload = JSON.parse(e.data);
|
||||||
if (payload.client !== uuid) {
|
if (payload.client !== uuid) {
|
||||||
|
console.log(payload.doc);
|
||||||
area.value = payload.doc;
|
area.value = payload.doc;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -49,6 +53,7 @@ function onSelectionChange() {
|
|||||||
const activeElement = document.activeElement;
|
const activeElement = document.activeElement;
|
||||||
|
|
||||||
if (activeElement && activeElement.id === areaId) {
|
if (activeElement && activeElement.id === areaId) {
|
||||||
|
console.debug("onSelectionChange", area);
|
||||||
selectionStart = area.selectionStart;
|
selectionStart = area.selectionStart;
|
||||||
selectionEnd = area.selectionEnd;
|
selectionEnd = area.selectionEnd;
|
||||||
}
|
}
|
||||||
@ -56,6 +61,15 @@ function onSelectionChange() {
|
|||||||
|
|
||||||
function onInput(event) {
|
function onInput(event) {
|
||||||
if (!validEvents.includes(event.inputType)) return;
|
if (!validEvents.includes(event.inputType)) return;
|
||||||
|
|
||||||
|
// workaround for differences between firefox and chrome
|
||||||
|
// chrome does not fire selectionchange events on backspace/delete events,
|
||||||
|
// while firefox does
|
||||||
|
if (event.inputType === "deleteContentBackward") {
|
||||||
|
selectionStart = area.selectionStart;
|
||||||
|
selectionEnd = area.selectionEnd;
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
client: uuid,
|
client: uuid,
|
||||||
action: event.inputType,
|
action: event.inputType,
|
||||||
@ -66,10 +80,7 @@ function onInput(event) {
|
|||||||
|
|
||||||
ws.send(JSON.stringify(payload));
|
ws.send(JSON.stringify(payload));
|
||||||
|
|
||||||
console.log(event.inputType);
|
console.log(selectionStart, selectionEnd);
|
||||||
console.log(selectionStart);
|
|
||||||
console.log(selectionEnd);
|
|
||||||
console.log(event.data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setup();
|
setup();
|
||||||
|
@ -3,9 +3,7 @@ use std::sync::Arc;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::actions::delete::{DeleteContentBackward, DeleteContentForward};
|
use crate::actions::delete::{DeleteContentBackward, DeleteContentForward};
|
||||||
use crate::actions::insert::InsertText;
|
use crate::actions::insert::{InsertText, InsertFromPaste, InsertLineBreak};
|
||||||
use crate::actions::linebreak::InsertLineBreak;
|
|
||||||
use crate::actions::paste::InsertFromPaste;
|
|
||||||
use crate::actions::ActionRunner;
|
use crate::actions::ActionRunner;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
@ -40,7 +38,11 @@ impl Action {
|
|||||||
ActionType::DeleteContentForward => Box::new(DeleteContentForward),
|
ActionType::DeleteContentForward => Box::new(DeleteContentForward),
|
||||||
};
|
};
|
||||||
|
|
||||||
*doc = action.run(self.start, self.end, self.data.clone(), doc.clone());
|
let data = match self.data.to_owned() {
|
||||||
|
None => Vec::new(),
|
||||||
|
Some(data) => data.encode_utf16().collect()
|
||||||
|
};
|
||||||
|
*doc = action.run(self.start, self.end, data, doc.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,45 @@
|
|||||||
use super::ActionRunner;
|
use super::ActionRunner;
|
||||||
|
|
||||||
pub(crate) struct DeleteContentBackward;
|
pub(crate) struct DeleteContentBackward;
|
||||||
|
|
||||||
pub(crate) struct DeleteContentForward;
|
pub(crate) struct DeleteContentForward;
|
||||||
|
|
||||||
impl ActionRunner for DeleteContentBackward {
|
fn delete(start: usize, end: usize, mut doc: Vec<u16>) -> Vec<u16> {
|
||||||
fn run(&self, start: usize, _end: usize, _data: Option<String>, mut doc: String) -> String {
|
if start > doc.len() + 1 {
|
||||||
if start > 0 {
|
return doc;
|
||||||
doc.remove(start - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut start_idx = start;
|
||||||
|
let mut end_idx = end;
|
||||||
|
unsafe {
|
||||||
|
while start_idx > 0 && matches!(doc.get_unchecked(start_idx), 0xDC00..=0xDFFF) {
|
||||||
|
start_idx -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let end_byte = doc.get_unchecked(end_idx - 1);
|
||||||
|
if end_idx < doc.len() && end_byte > &0xD800 && end_byte < &0xDC00 {
|
||||||
|
end_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.drain(start_idx..end_idx);
|
||||||
doc
|
doc
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActionRunner for DeleteContentBackward {
|
||||||
|
fn run(&self, start: usize, end: usize, _data: Vec<u16>, doc: Vec<u16>) -> Vec<u16> {
|
||||||
|
if (start == 0 && start == end ) || end > doc.len() { return doc; }
|
||||||
|
let (end, start) = if start == end { (start, start - 1) } else { (end, start) };
|
||||||
|
|
||||||
|
delete(start, end, doc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActionRunner for DeleteContentForward {
|
impl ActionRunner for DeleteContentForward {
|
||||||
fn run(&self, start: usize, _end: usize, _data: Option<String>, mut doc: String) -> String {
|
fn run(&self, start: usize, end: usize, _data: Vec<u16>, doc: Vec<u16>) -> Vec<u16> {
|
||||||
if doc.len() > start {
|
if start >= doc.len() { return doc; }
|
||||||
doc.remove(start);
|
|
||||||
}
|
let end = if start == end { start + 1 } else { end };
|
||||||
doc
|
delete(start, end, doc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,38 @@
|
|||||||
use super::ActionRunner;
|
use super::ActionRunner;
|
||||||
|
|
||||||
pub(crate) struct InsertText;
|
pub(crate) struct InsertText;
|
||||||
|
pub(crate) struct InsertFromPaste;
|
||||||
|
pub(crate) struct InsertLineBreak;
|
||||||
|
|
||||||
impl ActionRunner for InsertText {
|
const NL_BYTES: [u16; 1] = [10];
|
||||||
fn run(&self, start: usize, end: usize, data: Option<String>, mut doc: String) -> String {
|
|
||||||
if doc.len() < start || data.is_none() {
|
fn insert(start: usize, end: usize, data: Vec<u16>, mut doc: Vec<u16>) -> Vec<u16> {
|
||||||
|
if doc.len() < start || data.is_empty() {
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if start < end {
|
if start < end {
|
||||||
doc.replace_range(start..end, "");
|
doc.drain(start..end);
|
||||||
}
|
}
|
||||||
|
|
||||||
doc.insert_str(start, &data.unwrap());
|
doc.splice(start..start, data);
|
||||||
doc
|
doc
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActionRunner for InsertText {
|
||||||
|
fn run(&self, start: usize, end: usize, data: Vec<u16>, doc: Vec<u16>) -> Vec<u16> {
|
||||||
|
insert(start, end, data, doc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActionRunner for InsertFromPaste {
|
||||||
|
fn run(&self, start: usize, end: usize, data: Vec<u16>, doc: Vec<u16>) -> Vec<u16> {
|
||||||
|
insert(start, end, data, doc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActionRunner for InsertLineBreak {
|
||||||
|
fn run(&self, start: usize, _end: usize, _data: Vec<u16>, doc: Vec<u16>) -> Vec<u16> {
|
||||||
|
insert(start, start, Vec::from(NL_BYTES), doc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
use super::ActionRunner;
|
|
||||||
|
|
||||||
pub(crate) struct InsertLineBreak;
|
|
||||||
|
|
||||||
impl ActionRunner for InsertLineBreak {
|
|
||||||
fn run(&self, start: usize, _end: usize, _data: Option<String>, mut doc: String) -> String {
|
|
||||||
if doc.len() >= start {
|
|
||||||
doc.insert(start, '\n');
|
|
||||||
}
|
|
||||||
doc
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,6 @@
|
|||||||
pub(crate) mod delete;
|
pub(crate) mod delete;
|
||||||
pub(crate) mod insert;
|
pub(crate) mod insert;
|
||||||
pub(crate) mod linebreak;
|
|
||||||
pub(crate) mod paste;
|
|
||||||
|
|
||||||
pub(crate) trait ActionRunner {
|
pub(crate) trait ActionRunner {
|
||||||
fn run(&self, start: usize, end: usize, data: Option<String>, doc: String) -> String;
|
fn run(&self, start: usize, end: usize, data: Vec<u16>, doc: Vec<u16>) -> Vec<u16>;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
use super::ActionRunner;
|
|
||||||
|
|
||||||
pub(crate) struct InsertFromPaste;
|
|
||||||
|
|
||||||
impl ActionRunner for InsertFromPaste {
|
|
||||||
fn run(&self, start: usize, end: usize, data: Option<String>, mut doc: String) -> String {
|
|
||||||
if doc.len() < start || data.is_none() {
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
if start < end {
|
|
||||||
doc.replace_range(start..end, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.insert_str(start, &data.unwrap());
|
|
||||||
doc
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,7 +13,7 @@ mod actions;
|
|||||||
mod ws;
|
mod ws;
|
||||||
|
|
||||||
struct AppState {
|
struct AppState {
|
||||||
doc: RwLock<String>,
|
doc: RwLock<Vec<u16>>,
|
||||||
tx: broadcast::Sender<String>,
|
tx: broadcast::Sender<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ async fn main() {
|
|||||||
|
|
||||||
let (tx, _rx) = broadcast::channel(100);
|
let (tx, _rx) = broadcast::channel(100);
|
||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
doc: RwLock::new("".to_string()),
|
doc: RwLock::new(Vec::new()),
|
||||||
tx,
|
tx,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ async fn handle_socket(stream: WebSocket, state: Arc<AppState>) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let doc = state.doc.read().await;
|
let doc = state.doc.read().await;
|
||||||
let doc: String = doc.clone();
|
let doc: String = String::from_utf16_lossy(&doc);
|
||||||
let new_state = NewState {
|
let new_state = NewState {
|
||||||
client: String::from("0000"),
|
client: String::from("0000"),
|
||||||
doc: doc.to_string(),
|
doc: doc.to_string(),
|
||||||
@ -64,7 +64,7 @@ async fn handle_socket(stream: WebSocket, state: Arc<AppState>) {
|
|||||||
|
|
||||||
let new_state = NewState {
|
let new_state = NewState {
|
||||||
client,
|
client,
|
||||||
doc: doc.to_string(),
|
doc: String::from_utf16_lossy(&doc),
|
||||||
};
|
};
|
||||||
let payload = serde_json::to_string(&new_state).unwrap();
|
let payload = serde_json::to_string(&new_state).unwrap();
|
||||||
let _ = tx.send(payload);
|
let _ = tx.send(payload);
|
||||||
|
Loading…
Reference in New Issue
Block a user