use crate::schema::{
ColorRole, DecorationKind, EditGroupStatus, GutterDecorationKind, HoverPhase, Paint, Severity,
};
use std::rc::Rc;
pub const DECORATION_LEN: usize = 44;
pub const SYNTAX_SPAN_LEN: usize = 28;
pub const CODE_EDIT_LEN: usize = 24;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RecordFieldType {
U32,
U64,
Role {
name: &'static str,
optional: bool,
},
Paint,
Flag,
F32,
Text,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RecordField {
pub name: &'static str,
pub offset: u16,
pub ty: RecordFieldType,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RecordSchema {
pub name: &'static str,
pub length: u16,
pub fields: &'static [RecordField],
}
const fn field(name: &'static str, offset: u16, ty: RecordFieldType) -> RecordField {
RecordField { name, offset, ty }
}
pub const DECORATION_RECORD: RecordSchema = RecordSchema {
name: "DecorationRecord",
length: DECORATION_LEN as u16,
fields: &[
field("version", 0, RecordFieldType::U32),
field(
"kind",
4,
RecordFieldType::Role {
name: "DecorationKind",
optional: false,
},
),
field(
"severity",
6,
RecordFieldType::Role {
name: "Severity",
optional: true,
},
),
field(
"color",
8,
RecordFieldType::Role {
name: "ColorRole",
optional: true,
},
),
field("startLine", 12, RecordFieldType::U32),
field("startColumn", 16, RecordFieldType::U32),
field("endLine", 20, RecordFieldType::U32),
field("endColumn", 24, RecordFieldType::U32),
field("id", 28, RecordFieldType::U64),
field("text", 36, RecordFieldType::Text),
],
};
pub const SYNTAX_SPAN_RECORD: RecordSchema = RecordSchema {
name: "SyntaxSpanRecord",
length: SYNTAX_SPAN_LEN as u16,
fields: &[
field("version", 0, RecordFieldType::U32),
field("startLine", 4, RecordFieldType::U32),
field("startColumn", 8, RecordFieldType::U32),
field("endLine", 12, RecordFieldType::U32),
field("endColumn", 16, RecordFieldType::U32),
field("paint", 20, RecordFieldType::Paint),
],
};
pub const FOLD_LEN: usize = 16;
pub const GUTTER_DECORATION_LEN: usize = 28;
pub const FOLD_RECORD: RecordSchema = RecordSchema {
name: "FoldRecord",
length: FOLD_LEN as u16,
fields: &[
field("version", 0, RecordFieldType::U32),
field("startLine", 4, RecordFieldType::U32),
field("endLine", 8, RecordFieldType::U32),
field("collapsed", 12, RecordFieldType::Flag),
],
};
pub const GUTTER_DECORATION_RECORD: RecordSchema = RecordSchema {
name: "GutterDecorationRecord",
length: GUTTER_DECORATION_LEN as u16,
fields: &[
field("version", 0, RecordFieldType::U32),
field("line", 4, RecordFieldType::U32),
field(
"kind",
8,
RecordFieldType::Role {
name: "GutterDecorationKind",
optional: false,
},
),
field(
"color",
10,
RecordFieldType::Role {
name: "ColorRole",
optional: true,
},
),
field("id", 12, RecordFieldType::U64),
field("glyph", 20, RecordFieldType::U32),
],
};
pub const CODE_EDITOR_COLORS_LEN: usize = 12;
const fn optional_color(name: &'static str, offset: u16) -> RecordField {
field(
name,
offset,
RecordFieldType::Role {
name: "ColorRole",
optional: true,
},
)
}
pub const CODE_EDITOR_COLORS_RECORD: RecordSchema = RecordSchema {
name: "CodeEditorColorsRecord",
length: CODE_EDITOR_COLORS_LEN as u16,
fields: &[
optional_color("container", 0),
optional_color("gutter", 2),
optional_color("lineNumber", 4),
optional_color("currentLineNumber", 6),
optional_color("currentLine", 8),
],
};
pub const VIEW_ZONE_LEN: usize = 16;
pub const VIEW_ZONE_RECORD: RecordSchema = RecordSchema {
name: "ViewZoneRecord",
length: VIEW_ZONE_LEN as u16,
fields: &[
field("afterLine", 0, RecordFieldType::U32),
field("heightLines", 4, RecordFieldType::F32),
field("text", 8, RecordFieldType::Text),
],
};
const fn hash_bytes(mut hash: u64, bytes: &[u8]) -> u64 {
let mut index = 0;
while index < bytes.len() {
hash ^= bytes[index] as u64;
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
index += 1;
}
hash
}
pub(crate) const fn hash_record_schema(mut hash: u64, schema: RecordSchema) -> u64 {
hash = hash_bytes(hash, schema.name.as_bytes());
hash = hash_bytes(hash, &schema.length.to_le_bytes());
let mut index = 0;
while index < schema.fields.len() {
let field = schema.fields[index];
hash = hash_bytes(hash, field.name.as_bytes());
hash = hash_bytes(hash, &field.offset.to_le_bytes());
hash = match field.ty {
RecordFieldType::U32 => hash_bytes(hash, &[1]),
RecordFieldType::U64 => hash_bytes(hash, &[2]),
RecordFieldType::Role { name, optional } => {
let hash = hash_bytes(hash, &[3, optional as u8]);
hash_bytes(hash, name.as_bytes())
}
RecordFieldType::Paint => hash_bytes(hash, &[4]),
RecordFieldType::Text => hash_bytes(hash, &[5]),
RecordFieldType::Flag => hash_bytes(hash, &[6]),
RecordFieldType::F32 => hash_bytes(hash, &[7]),
};
index += 1;
}
hash
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Position {
pub line: u32,
pub column: u32,
}
impl Position {
pub const fn new(line: u32, column: u32) -> Self {
Self { line, column }
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct CodeRange {
pub start: Position,
pub end: Position,
}
impl CodeRange {
pub const fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
pub const fn at(position: Position) -> Self {
Self {
start: position,
end: position,
}
}
pub const fn of(start_line: u32, start_column: u32, end_line: u32, end_column: u32) -> Self {
Self {
start: Position::new(start_line, start_column),
end: Position::new(end_line, end_column),
}
}
pub fn is_empty(&self) -> bool {
self.start == self.end
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Decoration {
pub version: u32,
pub kind: DecorationKind,
pub severity: Option<Severity>,
pub color: Option<ColorRole>,
pub range: CodeRange,
pub id: u64,
pub text: String,
}
impl Decoration {
pub fn underline(version: u32, range: CodeRange, severity: Severity) -> Self {
Self {
version,
kind: DecorationKind::Underline,
severity: Some(severity),
color: None,
range,
id: 0,
text: String::new(),
}
}
pub fn code_lens(version: u32, line: u32, text: impl Into<String>, id: u64) -> Self {
Self {
version,
kind: DecorationKind::CodeLens,
severity: None,
color: None,
range: CodeRange::at(Position::new(line, 0)),
id,
text: text.into(),
}
}
pub fn hover_anchor(version: u32, range: CodeRange, id: u64) -> Self {
Self {
version,
kind: DecorationKind::HoverAnchor,
severity: None,
color: None,
range,
id,
text: String::new(),
}
}
pub fn ghost_text(version: u32, at: Position, text: impl Into<String>, id: u64) -> Self {
Self {
version,
kind: DecorationKind::GhostText,
severity: None,
color: None,
range: CodeRange::at(at),
id,
text: text.into(),
}
}
pub fn line_background(version: u32, line_start: u32, line_end: u32, color: ColorRole) -> Self {
Self {
version,
kind: DecorationKind::LineBackground,
severity: None,
color: Some(color),
range: CodeRange::of(line_start, 0, line_end, 0),
id: 0,
text: String::new(),
}
}
pub fn range_background(version: u32, range: CodeRange, color: ColorRole) -> Self {
Self {
version,
kind: DecorationKind::RangeBackground,
severity: None,
color: Some(color),
range,
id: 0,
text: String::new(),
}
}
pub fn with_color(mut self, role: ColorRole) -> Self {
self.color = Some(role);
self
}
pub fn with_id(mut self, id: u64) -> Self {
self.id = id;
self
}
}
fn put_u16(out: &mut Vec<u8>, value: u16) {
out.extend_from_slice(&value.to_le_bytes());
}
fn put_u32(out: &mut Vec<u8>, value: u32) {
out.extend_from_slice(&value.to_le_bytes());
}
fn put_u64(out: &mut Vec<u8>, value: u64) {
out.extend_from_slice(&value.to_le_bytes());
}
fn put_range(out: &mut Vec<u8>, range: CodeRange) {
put_u32(out, range.start.line);
put_u32(out, range.start.column);
put_u32(out, range.end.line);
put_u32(out, range.end.column);
}
fn word(record: &[u8], at: usize) -> u32 {
u32::from_le_bytes([record[at], record[at + 1], record[at + 2], record[at + 3]])
}
fn half(record: &[u8], at: usize) -> u16 {
u16::from_le_bytes([record[at], record[at + 1]])
}
fn long(record: &[u8], at: usize) -> u64 {
u64::from(word(record, at)) | (u64::from(word(record, at + 4)) << 32)
}
fn range_at(record: &[u8], at: usize) -> CodeRange {
CodeRange::of(
word(record, at),
word(record, at + 4),
word(record, at + 8),
word(record, at + 12),
)
}
#[derive(Clone, Default, PartialEq)]
pub struct Decorations {
bytes: Option<Rc<[u8]>>,
}
impl std::fmt::Debug for Decorations {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Decorations")
.field("decorations", &self.decorations().len())
.finish()
}
}
impl Decorations {
pub fn new(decorations: impl IntoIterator<Item = Decoration>) -> Self {
let decorations: Vec<Decoration> = decorations.into_iter().collect();
let records_len = decorations.len() * DECORATION_LEN;
let mut bytes = Vec::with_capacity(records_len);
let mut texts = Vec::new();
for decoration in &decorations {
put_u32(&mut bytes, decoration.version);
put_u16(&mut bytes, decoration.kind as u16);
put_u16(
&mut bytes,
decoration.severity.map_or(0, |value| value as u16),
);
put_u16(&mut bytes, decoration.color.map_or(0, |value| value as u16));
put_u16(&mut bytes, 0);
put_range(&mut bytes, decoration.range);
put_u64(&mut bytes, decoration.id);
put_u32(&mut bytes, (records_len + texts.len()) as u32);
put_u32(&mut bytes, decoration.text.len() as u32);
texts.extend_from_slice(decoration.text.as_bytes());
}
bytes.extend_from_slice(&texts);
Self::from_bytes(bytes)
}
pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
let bytes = bytes.into();
Self {
bytes: (!bytes.is_empty()).then_some(bytes),
}
}
pub fn as_bytes(&self) -> &[u8] {
self.bytes.as_deref().unwrap_or(&[])
}
pub fn is_empty(&self) -> bool {
self.bytes.is_none()
}
pub fn decorations(&self) -> Vec<Decoration> {
let bytes = self.as_bytes();
let mut end = bytes.len();
let mut at = 0;
let mut found = Vec::new();
while at + DECORATION_LEN <= end {
let record = &bytes[at..at + DECORATION_LEN];
at += DECORATION_LEN;
let text_offset = word(record, 36) as usize;
let text_len = word(record, 40) as usize;
if text_len > 0 {
end = end.min(text_offset);
}
let Ok(kind) = DecorationKind::try_from(half(record, 4)) else {
continue;
};
let severity = match half(record, 6) {
0 => None,
tag => match Severity::try_from(tag) {
Ok(severity) => Some(severity),
Err(()) => continue,
},
};
let color = match half(record, 8) {
0 => None,
tag => match ColorRole::try_from(tag) {
Ok(role) => Some(role),
Err(()) => continue,
},
};
let text = if text_len == 0 {
String::new()
} else {
match bytes
.get(text_offset..text_offset + text_len)
.and_then(|run| std::str::from_utf8(run).ok())
{
Some(text) => text.to_owned(),
None => continue,
}
};
found.push(Decoration {
version: word(record, 0),
kind,
severity,
color,
range: range_at(record, 12),
id: long(record, 28),
text,
});
}
found
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SyntaxSpan {
pub version: u32,
pub range: CodeRange,
pub paint: Paint,
}
impl SyntaxSpan {
pub const fn new(version: u32, range: CodeRange, paint: Paint) -> Self {
Self {
version,
range,
paint,
}
}
pub const fn role(version: u32, range: CodeRange, role: ColorRole) -> Self {
Self::new(version, range, Paint::Role(role))
}
}
#[derive(Clone, Default, PartialEq)]
pub struct SyntaxSpans {
bytes: Option<Rc<[u8]>>,
}
impl std::fmt::Debug for SyntaxSpans {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("SyntaxSpans")
.field("spans", &(self.as_bytes().len() / SYNTAX_SPAN_LEN))
.finish()
}
}
impl SyntaxSpans {
pub fn new(spans: impl IntoIterator<Item = SyntaxSpan>) -> Self {
let mut bytes = Vec::new();
for span in spans {
put_u32(&mut bytes, span.version);
put_range(&mut bytes, span.range);
put_u64(&mut bytes, span.paint.to_bits());
}
Self::from_bytes(bytes)
}
pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
let bytes = bytes.into();
Self {
bytes: (!bytes.is_empty()).then_some(bytes),
}
}
pub fn as_bytes(&self) -> &[u8] {
self.bytes.as_deref().unwrap_or(&[])
}
pub fn is_empty(&self) -> bool {
self.bytes.is_none()
}
pub fn spans(&self) -> Vec<SyntaxSpan> {
self.as_bytes()
.chunks_exact(SYNTAX_SPAN_LEN)
.filter_map(|record| {
Some(SyntaxSpan {
version: word(record, 0),
range: range_at(record, 4),
paint: Paint::from_bits(long(record, 20))?,
})
})
.collect()
}
}
pub fn byte_offset(text: &str, position: Position) -> Option<usize> {
let bytes = text.as_bytes();
let mut start = 0;
for _ in 0..position.line {
let at = bytes[start..]
.iter()
.position(|byte| *byte == b'\n' || *byte == b'\r')?
+ start;
start = if bytes[at] == b'\r' && bytes.get(at + 1) == Some(&b'\n') {
at + 2
} else {
at + 1
};
}
let end = bytes[start..]
.iter()
.position(|byte| *byte == b'\n' || *byte == b'\r')
.map_or(bytes.len(), |offset| start + offset);
let mut units = 0_u32;
let mut offset = start;
for character in text[start..end].chars() {
if units == position.column {
return Some(offset);
}
units += character.len_utf16() as u32;
offset += character.len_utf8();
if units > position.column {
return None;
}
}
(units == position.column).then_some(offset)
}
#[derive(Clone, Debug, PartialEq)]
pub struct CodeChange {
pub version: u32,
pub range: CodeRange,
pub text: String,
}
impl CodeChange {
pub fn apply_to(&self, text: &mut String) -> bool {
let (Some(start), Some(end)) = (
byte_offset(text, self.range.start),
byte_offset(text, self.range.end),
) else {
return false;
};
if start > end {
return false;
}
text.replace_range(start..end, &self.text);
true
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EditRejected {
pub request_id: u32,
pub base_version: u32,
pub current_version: u32,
pub range: CodeRange,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CodeHover {
pub decoration: Option<u64>,
pub position: Position,
pub phase: HoverPhase,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SaveRequest {
pub version: u32,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Fold {
pub version: u32,
pub start_line: u32,
pub end_line: u32,
pub collapsed: bool,
}
impl Fold {
pub const fn new(version: u32, start_line: u32, end_line: u32) -> Self {
Self {
version,
start_line,
end_line,
collapsed: false,
}
}
pub const fn collapsed(mut self) -> Self {
self.collapsed = true;
self
}
}
#[derive(Clone, Default, PartialEq)]
pub struct Folds {
bytes: Option<Rc<[u8]>>,
}
impl std::fmt::Debug for Folds {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Folds")
.field("folds", &(self.as_bytes().len() / FOLD_LEN))
.finish()
}
}
impl Folds {
pub fn new(folds: impl IntoIterator<Item = Fold>) -> Self {
let mut bytes = Vec::new();
for fold in folds {
put_u32(&mut bytes, fold.version);
put_u32(&mut bytes, fold.start_line);
put_u32(&mut bytes, fold.end_line);
put_u32(&mut bytes, u32::from(fold.collapsed));
}
Self::from_bytes(bytes)
}
pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
let bytes = bytes.into();
Self {
bytes: (!bytes.is_empty()).then_some(bytes),
}
}
pub fn as_bytes(&self) -> &[u8] {
self.bytes.as_deref().unwrap_or(&[])
}
pub fn is_empty(&self) -> bool {
self.bytes.is_none()
}
pub fn folds(&self) -> Vec<Fold> {
self.as_bytes()
.chunks_exact(FOLD_LEN)
.filter_map(|record| {
Some(Fold {
version: word(record, 0),
start_line: word(record, 4),
end_line: word(record, 8),
collapsed: match word(record, 12) {
0 => false,
1 => true,
_ => return None,
},
})
})
.collect()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GutterDecoration {
pub version: u32,
pub line: u32,
pub kind: GutterDecorationKind,
pub color: Option<ColorRole>,
pub id: u64,
pub glyph: u32,
}
impl GutterDecoration {
pub const fn dot(version: u32, line: u32, id: u64) -> Self {
Self {
version,
line,
kind: GutterDecorationKind::Dot,
color: None,
id,
glyph: 0,
}
}
pub const fn bar(version: u32, line: u32, id: u64) -> Self {
Self {
version,
line,
kind: GutterDecorationKind::Bar,
color: None,
id,
glyph: 0,
}
}
pub const fn glyph(version: u32, line: u32, glyph: char, id: u64) -> Self {
Self {
version,
line,
kind: GutterDecorationKind::Glyph,
color: None,
id,
glyph: glyph as u32,
}
}
pub const fn with_color(mut self, role: ColorRole) -> Self {
self.color = Some(role);
self
}
pub const fn with_id(mut self, id: u64) -> Self {
self.id = id;
self
}
}
#[derive(Clone, Default, PartialEq)]
pub struct GutterDecorations {
bytes: Option<Rc<[u8]>>,
}
impl std::fmt::Debug for GutterDecorations {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("GutterDecorations")
.field(
"decorations",
&(self.as_bytes().len() / GUTTER_DECORATION_LEN),
)
.finish()
}
}
impl GutterDecorations {
pub fn new(decorations: impl IntoIterator<Item = GutterDecoration>) -> Self {
let mut bytes = Vec::new();
for decoration in decorations {
put_u32(&mut bytes, decoration.version);
put_u32(&mut bytes, decoration.line);
put_u16(&mut bytes, decoration.kind as u16);
put_u16(&mut bytes, decoration.color.map_or(0, |value| value as u16));
put_u64(&mut bytes, decoration.id);
put_u32(&mut bytes, decoration.glyph);
put_u32(&mut bytes, 0);
}
Self::from_bytes(bytes)
}
pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
let bytes = bytes.into();
Self {
bytes: (!bytes.is_empty()).then_some(bytes),
}
}
pub fn as_bytes(&self) -> &[u8] {
self.bytes.as_deref().unwrap_or(&[])
}
pub fn is_empty(&self) -> bool {
self.bytes.is_none()
}
pub fn decorations(&self) -> Vec<GutterDecoration> {
self.as_bytes()
.chunks_exact(GUTTER_DECORATION_LEN)
.filter_map(|record| {
let kind = GutterDecorationKind::try_from(half(record, 8)).ok()?;
let color = match half(record, 10) {
0 => None,
tag => Some(ColorRole::try_from(tag).ok()?),
};
Some(GutterDecoration {
version: word(record, 0),
line: word(record, 4),
kind,
color,
id: long(record, 12),
glyph: word(record, 20),
})
})
.collect()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ViewZone {
pub after_line: u32,
pub height_lines: f32,
pub text: String,
}
impl ViewZone {
pub fn new(after_line: u32, height_lines: f32) -> Self {
Self {
after_line,
height_lines,
text: String::new(),
}
}
pub fn with_text(mut self, text: impl Into<String>) -> Self {
self.text = text.into();
self
}
}
#[derive(Clone, Default, PartialEq)]
pub struct ViewZones {
bytes: Option<Rc<[u8]>>,
}
impl std::fmt::Debug for ViewZones {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ViewZones")
.field("zones", &self.zones().len())
.finish()
}
}
impl ViewZones {
pub fn new(zones: impl IntoIterator<Item = ViewZone>) -> Self {
let zones: Vec<ViewZone> = zones.into_iter().collect();
let records_len = zones.len() * VIEW_ZONE_LEN;
let mut bytes = Vec::with_capacity(records_len);
let mut texts = Vec::new();
for zone in &zones {
put_u32(&mut bytes, zone.after_line);
put_u32(&mut bytes, zone.height_lines.to_bits());
put_u32(&mut bytes, (records_len + texts.len()) as u32);
put_u32(&mut bytes, zone.text.len() as u32);
texts.extend_from_slice(zone.text.as_bytes());
}
bytes.extend_from_slice(&texts);
Self::from_bytes(bytes)
}
pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
let bytes = bytes.into();
Self {
bytes: (!bytes.is_empty()).then_some(bytes),
}
}
pub fn as_bytes(&self) -> &[u8] {
self.bytes.as_deref().unwrap_or(&[])
}
pub fn is_empty(&self) -> bool {
self.bytes.is_none()
}
pub fn zones(&self) -> Vec<ViewZone> {
let bytes = self.as_bytes();
let mut end = bytes.len();
let mut at = 0;
let mut found = Vec::new();
while at + VIEW_ZONE_LEN <= end {
let record = &bytes[at..at + VIEW_ZONE_LEN];
at += VIEW_ZONE_LEN;
let text_offset = word(record, 8) as usize;
let text_len = word(record, 12) as usize;
if text_len > 0 {
end = end.min(text_offset);
}
let text = if text_len == 0 {
String::new()
} else {
match bytes
.get(text_offset..text_offset + text_len)
.and_then(|run| std::str::from_utf8(run).ok())
{
Some(text) => text.to_owned(),
None => continue,
}
};
found.push(ViewZone {
after_line: word(record, 0),
height_lines: f32::from_bits(word(record, 4)),
text,
});
}
found
}
}
pub fn escape_snippet_text(text: &str) -> String {
let mut escaped = String::with_capacity(text.len());
for character in text.chars() {
if matches!(character, '$' | '}' | '\\') {
escaped.push('\\');
}
escaped.push(character);
}
escaped
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum WordWrap {
#[default]
Off,
Viewport,
Column(u32),
Bounded(u32),
}
impl WordWrap {
pub fn to_value(self) -> i64 {
let (mode, columns) = match self {
Self::Off => (crate::schema::WordWrapMode::Off, 0),
Self::Viewport => (crate::schema::WordWrapMode::Viewport, 0),
Self::Column(columns) => (crate::schema::WordWrapMode::Column, columns),
Self::Bounded(columns) => (crate::schema::WordWrapMode::Bounded, columns),
};
i64::from(u16::from(mode)) | (i64::from(columns) << 16)
}
pub fn from_value(value: i64) -> Option<Self> {
let columns = u32::try_from(value >> 16).ok()?;
let mode = u16::try_from(value & 0xffff).ok()?;
match mode {
0 if columns == 0 => Some(Self::Off),
_ if mode == u16::from(crate::schema::WordWrapMode::Viewport) && columns == 0 => {
Some(Self::Viewport)
}
_ if mode == u16::from(crate::schema::WordWrapMode::Column) && columns > 0 => {
Some(Self::Column(columns))
}
_ if mode == u16::from(crate::schema::WordWrapMode::Bounded) && columns > 0 => {
Some(Self::Bounded(columns))
}
_ => None,
}
}
}
pub const SELECTION_LEN: usize = 16;
pub fn encode_selections(selections: &[(Position, Position)], out: &mut Vec<u8>) {
out.reserve(selections.len() * SELECTION_LEN);
for (anchor, active) in selections {
put_u32(out, anchor.line);
put_u32(out, anchor.column);
put_u32(out, active.line);
put_u32(out, active.column);
}
}
pub fn decode_selections(bytes: &[u8]) -> Option<Vec<(Position, Position)>> {
if bytes.len() % SELECTION_LEN != 0 {
return None;
}
Some(
bytes
.chunks_exact(SELECTION_LEN)
.map(|record| {
(
Position::new(word(record, 0), word(record, 4)),
Position::new(word(record, 8), word(record, 12)),
)
})
.collect(),
)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SelectionsChange {
pub version: u32,
pub selections: Vec<(Position, Position)>,
}
impl SelectionsChange {
pub fn from_payload(payload: &crate::schema::EventPayload<'_>) -> Option<Self> {
match payload {
crate::schema::EventPayload::SelectionsChanged {
version,
selections,
} => Some(Self {
version: *version,
selections: decode_selections(selections)?,
}),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SelectionChange {
pub version: u32,
pub anchor: Position,
pub active: Position,
}
impl SelectionChange {
pub fn is_caret(&self) -> bool {
self.anchor == self.active
}
pub fn range(&self) -> CodeRange {
CodeRange::new(self.anchor.min(self.active), self.anchor.max(self.active))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VisibleRange {
pub version: u32,
pub first_line: u32,
pub last_line: u32,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FoldChange {
pub start_line: u32,
pub collapsed: bool,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct CodeEditorColors {
pub container: Option<ColorRole>,
pub gutter: Option<ColorRole>,
pub line_number: Option<ColorRole>,
pub current_line_number: Option<ColorRole>,
pub current_line: Option<ColorRole>,
}
impl CodeEditorColors {
pub const fn new() -> Self {
Self {
container: None,
gutter: None,
line_number: None,
current_line_number: None,
current_line: None,
}
}
pub const fn container(mut self, role: ColorRole) -> Self {
self.container = Some(role);
self
}
pub const fn gutter(mut self, role: ColorRole) -> Self {
self.gutter = Some(role);
self
}
pub const fn line_number(mut self, role: ColorRole) -> Self {
self.line_number = Some(role);
self
}
pub const fn current_line_number(mut self, role: ColorRole) -> Self {
self.current_line_number = Some(role);
self
}
pub const fn current_line(mut self, role: ColorRole) -> Self {
self.current_line = Some(role);
self
}
pub fn is_empty(&self) -> bool {
*self == Self::new()
}
pub fn to_bytes(&self) -> [u8; CODE_EDITOR_COLORS_LEN] {
let mut bytes = [0_u8; CODE_EDITOR_COLORS_LEN];
let slots = [
self.container,
self.gutter,
self.line_number,
self.current_line_number,
self.current_line,
];
for (index, slot) in slots.into_iter().enumerate() {
let tag = slot.map_or(0, |role| role as u16);
bytes[index * 2..index * 2 + 2].copy_from_slice(&tag.to_le_bytes());
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != CODE_EDITOR_COLORS_LEN {
return None;
}
let slot = |index: usize| -> Option<Option<ColorRole>> {
match half(bytes, index * 2) {
0 => Some(None),
tag => ColorRole::try_from(tag).ok().map(Some),
}
};
Some(Self {
container: slot(0)?,
gutter: slot(1)?,
line_number: slot(2)?,
current_line_number: slot(3)?,
current_line: slot(4)?,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CodeMetrics {
pub line_height_px: f64,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CodeScroll {
pub offset_px: f64,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct CodeRect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
impl CodeRect {
pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
Self {
x,
y,
width,
height,
}
}
pub fn bottom(&self) -> f32 {
self.y + self.height
}
pub fn right(&self) -> f32 {
self.x + self.width
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CaretRect {
pub position: Position,
pub rect: CodeRect,
}
impl CaretRect {
pub fn from_payload(payload: &crate::schema::EventPayload<'_>) -> Option<Self> {
match *payload {
crate::schema::EventPayload::CaretRect { line, column, rect } => Some(Self {
position: Position::new(line, column),
rect,
}),
_ => None,
}
}
}
pub type PositionAnswer = Option<CodeRect>;
type PositionCallback = Box<dyn FnOnce(PositionAnswer)>;
thread_local! {
static POSITION_QUERIES: std::cell::RefCell<Vec<(u32, PositionCallback)>> =
const { std::cell::RefCell::new(Vec::new()) };
static NEXT_POSITION_QUERY: std::cell::Cell<u32> = const { std::cell::Cell::new(1) };
}
pub fn position_query(on_answer: impl FnOnce(PositionAnswer) + 'static) -> u32 {
let request = NEXT_POSITION_QUERY.with(|next| {
let mut id = next.get();
if id == 0 {
id = 1;
}
next.set(id.wrapping_add(1));
id
});
POSITION_QUERIES.with_borrow_mut(|waiting| waiting.push((request, Box::new(on_answer))));
request
}
pub(crate) fn deliver_position_rect(request: u32, answer: PositionAnswer) -> bool {
let callback = POSITION_QUERIES.with_borrow_mut(|waiting| {
waiting
.iter()
.position(|(id, _)| *id == request)
.map(|at| waiting.remove(at).1)
});
match callback {
Some(callback) => {
callback(answer);
true
}
None => false,
}
}
#[doc(hidden)]
pub fn reset_position_queries() {
POSITION_QUERIES.with_borrow_mut(Vec::clear);
}
#[derive(Clone, Debug)]
pub struct CodePointer {
pub event: crate::PointerEvent,
pub position: Option<Position>,
}
impl CodePointer {
pub fn from_payload(payload: &crate::schema::EventPayload<'_>) -> Option<Self> {
match *payload {
crate::schema::EventPayload::CodePointer {
kind,
pointer_id,
pointer_type,
x,
y,
buttons,
button,
modifiers,
click_count,
delta_x,
delta_y,
delta_mode,
timestamp_nanos,
position,
} => Some(Self {
event: crate::PointerEvent::new(
kind,
pointer_id,
pointer_type,
x,
y,
buttons,
button,
modifiers,
click_count,
delta_x,
delta_y,
delta_mode,
timestamp_nanos,
),
position,
}),
_ => None,
}
}
}
pub fn encode_edit_group(
edits: &[(CodeRange, &str)],
out: &mut Vec<u8>,
) -> Result<(), crate::protocol::ProtocolError> {
use crate::protocol::ProtocolError;
let count = u32::try_from(edits.len()).map_err(|_| ProtocolError::LengthOverflow)?;
let texts: usize = edits.iter().map(|(_, text)| text.len()).sum();
out.reserve(4 + edits.len() * CODE_EDIT_LEN + texts);
put_u32(out, count);
let mut offset = 0_u32;
for (range, text) in edits {
let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
put_range(out, *range);
put_u32(out, offset);
put_u32(out, len);
offset = offset
.checked_add(len)
.ok_or(ProtocolError::LengthOverflow)?;
}
for (_, text) in edits {
out.extend_from_slice(text.as_bytes());
}
Ok(())
}
pub fn decode_edit_group(bytes: &[u8]) -> Option<Vec<(CodeRange, &str)>> {
if bytes.len() < 4 {
return None;
}
let count = word(bytes, 0) as usize;
let texts_start = count.checked_mul(CODE_EDIT_LEN)?.checked_add(4)?;
if texts_start > bytes.len() {
return None;
}
let mut edits = Vec::with_capacity(count);
let mut texts_end = texts_start;
for index in 0..count {
let record = &bytes[4 + index * CODE_EDIT_LEN..4 + (index + 1) * CODE_EDIT_LEN];
let start = texts_start.checked_add(word(record, 16) as usize)?;
let end = start.checked_add(word(record, 20) as usize)?;
let text = std::str::from_utf8(bytes.get(start..end)?).ok()?;
texts_end = texts_end.max(end);
edits.push((range_at(record, 0), text));
}
(texts_end == bytes.len()).then_some(edits)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EditGroupResult {
pub request_id: u32,
pub status: EditGroupStatus,
pub base_version: u32,
pub current_version: u32,
pub index: u32,
}
impl EditGroupResult {
pub fn from_payload(payload: &crate::schema::EventPayload<'_>) -> Option<Self> {
match *payload {
crate::schema::EventPayload::CodeEditGroupResult {
request_id,
status,
base_version,
current_version,
index,
} => Some(Self {
request_id,
status,
base_version,
current_version,
index,
}),
_ => None,
}
}
}