use std::collections::BTreeMap;
pub const FEATURE_LSP: u32 = 1 << 8;
pub const C2S_LSP_OPEN: u8 = 0x60;
pub const C2S_LSP_CLOSE: u8 = 0x61;
pub const C2S_LSP_ACK: u8 = 0x62;
pub const C2S_LSP_QUERY: u8 = 0x63;
pub const C2S_LSP_CANCEL: u8 = 0x64;
pub const C2S_LSP_SERVERS: u8 = 0x65;
pub const C2S_LSP_STOP: u8 = 0x66;
pub const C2S_LSP_BUFFER: u8 = 0x67;
pub const S2C_LSP_OPENED: u8 = 0x60;
pub const S2C_LSP_STATE: u8 = 0x61;
pub const S2C_LSP_DIAG: u8 = 0x62;
pub const S2C_LSP_QUERY: u8 = 0x63;
pub const S2C_LSP_CLOSED: u8 = 0x64;
pub const S2C_LSP_SERVERS: u8 = 0x65;
pub const S2C_LSP_STOPPED: u8 = 0x66;
pub const LSP_STATUS_OK: u8 = 0;
pub const LSP_STATUS_UNKNOWN_ID: u8 = 1;
pub const LSP_STATUS_NOT_FOUND: u8 = 2;
pub const LSP_STATUS_WRONG_TYPE: u8 = 3;
pub const LSP_STATUS_PERMISSION: u8 = 4;
pub const LSP_STATUS_TOO_LARGE: u8 = 5;
pub const LSP_STATUS_BUDGET: u8 = 6;
pub const LSP_STATUS_INVALID: u8 = 7;
pub const LSP_STATUS_CANCELLED: u8 = 8;
pub const LSP_STATUS_OTHER: u8 = 9;
pub const LSP_STATUS_WARMING: u8 = 10;
pub fn lsp_status_text(status: u8) -> &'static str {
match status {
LSP_STATUS_OK => "ok",
LSP_STATUS_UNKNOWN_ID => "unknown attachment",
LSP_STATUS_NOT_FOUND => "not found",
LSP_STATUS_WRONG_TYPE => "wrong type",
LSP_STATUS_PERMISSION => "permission denied",
LSP_STATUS_TOO_LARGE => "too large",
LSP_STATUS_BUDGET => "budget exhausted",
LSP_STATUS_INVALID => "invalid request",
LSP_STATUS_CANCELLED => "cancelled",
LSP_STATUS_WARMING => "warming up",
_ => "error",
}
}
pub const LSP_OPEN_WATCH: u8 = 1 << 0;
pub const LSP_OPEN_DIAGS: u8 = 1 << 1;
pub const LSP_OPEN_FROM_PTY: u8 = 1 << 2;
pub const LSP_ID_INVALID: u16 = 0xFFFF;
pub const LSP_STREAM_STATE: u8 = 0;
pub const LSP_STREAM_DIAG: u8 = 1;
pub const LSP_QUERY_DEFINITION: u8 = 1;
pub const LSP_QUERY_REFERENCES: u8 = 2;
pub const LSP_QUERY_HOVER: u8 = 3;
pub const LSP_QUERY_DOC_SYMBOLS: u8 = 4;
pub const LSP_QUERY_WS_SYMBOLS: u8 = 5;
pub const LSP_QUERY_RENAME: u8 = 6;
pub const LSP_QUERY_COMPLETION: u8 = 7;
pub const LSP_QUERY_SIGNATURE: u8 = 8;
pub const LSP_REFS_INCLUDE_DECLARATION: u8 = 1 << 0;
pub const LSP_BUFFER_RELEASE: u8 = 1 << 0;
pub const LSP_DIAG_FULL: u8 = 1 << 0;
pub const LSP_RESP_TRUNCATED: u8 = 1 << 0;
pub const LSP_RESP_INCOMPLETE: u8 = 1 << 1;
pub const LSP_CLOSED_CLIENT_REQUEST: u8 = 0;
pub const LSP_CLOSED_ROOT_GONE: u8 = 1;
pub const LSP_CLOSED_PERMISSION_LOST: u8 = 2;
pub const LSP_CLOSED_BACKEND_FAILED: u8 = 3;
pub const LSP_CLOSED_RESOURCE_LIMIT: u8 = 4;
pub const LSP_PHASE_SPAWNING: u8 = 0;
pub const LSP_PHASE_INITIALIZING: u8 = 1;
pub const LSP_PHASE_INDEXING: u8 = 2;
pub const LSP_PHASE_READY: u8 = 3;
pub const LSP_PHASE_FAILED: u8 = 4;
pub const LSP_PROGRESS_UNKNOWN: u8 = 255;
pub const LSP_CAP_DEFINITION: u32 = 1 << 0;
pub const LSP_CAP_REFERENCES: u32 = 1 << 1;
pub const LSP_CAP_HOVER: u32 = 1 << 2;
pub const LSP_CAP_DOC_SYMBOLS: u32 = 1 << 3;
pub const LSP_CAP_WS_SYMBOLS: u32 = 1 << 4;
pub const LSP_CAP_RENAME: u32 = 1 << 5;
pub const LSP_CAP_COMPLETION: u32 = 1 << 6;
pub const LSP_CAP_SIGNATURE: u32 = 1 << 7;
pub const LSP_COMPLETION_DEPRECATED: u8 = 1 << 0;
pub const LSP_COMPLETION_SNIPPET: u8 = 1 << 1;
pub const LSP_COMPLETION_PRESELECT: u8 = 1 << 2;
pub const LSP_SIGNATURE_ACTIVE: u8 = 1 << 0;
pub const LSP_SIGNATURE_NO_PARAM: u16 = 0xFFFF;
pub const LSP_SEVERITY_ERROR: u8 = 1;
pub const LSP_SEVERITY_WARNING: u8 = 2;
pub const LSP_SEVERITY_INFO: u8 = 3;
pub const LSP_SEVERITY_HINT: u8 = 4;
pub const LSP_DIAG_UNNECESSARY: u8 = 1 << 0;
pub const LSP_DIAG_DEPRECATED: u8 = 1 << 1;
pub const LSP_MARKUP_PLAIN: u8 = 0;
pub const LSP_MARKUP_MARKDOWN: u8 = 1;
pub const LSP_SYMBOL_DEPRECATED: u8 = 1 << 0;
pub const LSP_STATE_RECORD_SERVER: u8 = 0x01;
pub const LSP_DIAG_RECORD_FILE: u8 = 0x01;
pub const LSP_DIAG_RECORD_DIAG: u8 = 0x02;
pub const LSP_QUERY_RECORD_LOCATION: u8 = 0x01;
pub const LSP_QUERY_RECORD_MARKUP: u8 = 0x02;
pub const LSP_QUERY_RECORD_SYMBOL: u8 = 0x03;
pub const LSP_QUERY_RECORD_EDIT: u8 = 0x04;
pub const LSP_QUERY_RECORD_COMPLETION: u8 = 0x05;
pub const LSP_QUERY_RECORD_SIGNATURE: u8 = 0x06;
pub type LspHash = [u8; 16];
pub const LSP_HASH_NONE: LspHash = [0; 16];
fn decompress_guarded(data: &[u8]) -> Option<Vec<u8>> {
if data.len() < 4 {
return None;
}
let declared = u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize;
if declared > crate::MAX_DECOMPRESSED {
return None;
}
lz4_flex::decompress_size_prepended(data).ok()
}
fn push_str(buf: &mut Vec<u8>, s: &str) {
let b = s.as_bytes();
buf.extend_from_slice(&(b.len() as u16).to_le_bytes());
buf.extend_from_slice(b);
}
fn push_bytes(buf: &mut Vec<u8>, b: &[u8]) {
buf.extend_from_slice(&(b.len() as u32).to_le_bytes());
buf.extend_from_slice(b);
}
fn take_u8(b: &mut &[u8]) -> Option<u8> {
let (&x, rest) = b.split_first()?;
*b = rest;
Some(x)
}
fn take_u16(b: &mut &[u8]) -> Option<u16> {
if b.len() < 2 {
return None;
}
let v = u16::from_le_bytes([b[0], b[1]]);
*b = &b[2..];
Some(v)
}
fn take_u32(b: &mut &[u8]) -> Option<u32> {
if b.len() < 4 {
return None;
}
let v = u32::from_le_bytes(b[0..4].try_into().unwrap());
*b = &b[4..];
Some(v)
}
fn take_u64(b: &mut &[u8]) -> Option<u64> {
if b.len() < 8 {
return None;
}
let v = u64::from_le_bytes(b[0..8].try_into().unwrap());
*b = &b[8..];
Some(v)
}
fn take_hash(b: &mut &[u8]) -> Option<LspHash> {
if b.len() < 16 {
return None;
}
let hash: LspHash = b[0..16].try_into().unwrap();
*b = &b[16..];
Some(hash)
}
fn take_str<'a>(b: &mut &'a [u8]) -> Option<&'a str> {
let len = take_u16(b)? as usize;
if b.len() < len {
return None;
}
let s = std::str::from_utf8(&b[..len]).ok()?;
*b = &b[len..];
Some(s)
}
fn take_text<'a>(b: &mut &'a [u8]) -> Option<&'a str> {
let len = take_u32(b)? as usize;
if b.len() < len {
return None;
}
let s = std::str::from_utf8(&b[..len]).ok()?;
*b = &b[len..];
Some(s)
}
fn body_of(msg: &[u8], opcode: u8) -> Option<&[u8]> {
if msg.first() != Some(&opcode) {
return None;
}
Some(&msg[1..])
}
fn begin_record(buf: &mut Vec<u8>) -> usize {
let start = buf.len();
buf.extend_from_slice(&0u32.to_le_bytes());
start
}
fn end_record(buf: &mut [u8], start: usize) {
let len = (buf.len() - start - 4) as u32;
buf[start..start + 4].copy_from_slice(&len.to_le_bytes());
}
fn next_record<'a>(data: &mut &'a [u8]) -> Option<(u8, &'a [u8])> {
let mut b = *data;
let len = take_u32(&mut b)? as usize;
if b.len() < len || len == 0 {
return None;
}
let record = &b[..len];
*data = &b[len..];
Some((record[0], &record[1..]))
}
pub fn msg_lsp_open(nonce: u16, flags: u8, diag_latency_ms: u16, path: &str) -> Vec<u8> {
let mut msg = Vec::with_capacity(8 + path.len());
msg.push(C2S_LSP_OPEN);
msg.extend_from_slice(&nonce.to_le_bytes());
msg.push(flags);
msg.extend_from_slice(&diag_latency_ms.to_le_bytes());
push_str(&mut msg, path);
msg
}
pub fn msg_lsp_open_from_pty(
nonce: u16,
flags: u8,
diag_latency_ms: u16,
path: &str,
src_pty_id: u16,
) -> Vec<u8> {
let mut msg = msg_lsp_open(nonce, flags | LSP_OPEN_FROM_PTY, diag_latency_ms, path);
msg.extend_from_slice(&src_pty_id.to_le_bytes());
msg
}
pub fn lsp_open_src_pty(msg: &[u8]) -> Option<u16> {
if msg.first().copied() != Some(C2S_LSP_OPEN) || msg.len() < 8 {
return None;
}
if msg[3] & LSP_OPEN_FROM_PTY == 0 {
return None;
}
let path_len = u16::from_le_bytes([msg[6], msg[7]]) as usize;
let off = 8usize.checked_add(path_len)?;
let b = msg.get(off..off + 2)?;
Some(u16::from_le_bytes([b[0], b[1]]))
}
pub fn lsp_open_rebase(msg: &[u8], cwd: Option<&str>) -> Option<Vec<u8>> {
lsp_open_src_pty(msg)?;
let (nonce, flags, diag_ms, path) = parse_lsp_open(msg)?;
let joined = cwd.map(|dir| {
std::path::Path::new(dir)
.join(path)
.to_string_lossy()
.into_owned()
});
let eff = joined.as_deref().unwrap_or(path);
Some(msg_lsp_open(
nonce,
flags & !LSP_OPEN_FROM_PTY,
diag_ms,
eff,
))
}
pub fn parse_lsp_open(msg: &[u8]) -> Option<(u16, u8, u16, &str)> {
let mut b = body_of(msg, C2S_LSP_OPEN)?;
let nonce = take_u16(&mut b)?;
let flags = take_u8(&mut b)?;
let diag_latency_ms = take_u16(&mut b)?;
let path = take_str(&mut b)?;
Some((nonce, flags, diag_latency_ms, path))
}
pub fn msg_lsp_close(lsp_id: u16) -> Vec<u8> {
let mut msg = Vec::with_capacity(3);
msg.push(C2S_LSP_CLOSE);
msg.extend_from_slice(&lsp_id.to_le_bytes());
msg
}
pub fn parse_lsp_close(msg: &[u8]) -> Option<u16> {
let mut b = body_of(msg, C2S_LSP_CLOSE)?;
take_u16(&mut b)
}
pub fn msg_lsp_ack(lsp_id: u16, stream: u8, update_id: u32) -> Vec<u8> {
let mut msg = Vec::with_capacity(8);
msg.push(C2S_LSP_ACK);
msg.extend_from_slice(&lsp_id.to_le_bytes());
msg.push(stream);
msg.extend_from_slice(&update_id.to_le_bytes());
msg
}
pub fn parse_lsp_ack(msg: &[u8]) -> Option<(u16, u8, u32)> {
let mut b = body_of(msg, C2S_LSP_ACK)?;
let lsp_id = take_u16(&mut b)?;
let stream = take_u8(&mut b)?;
let update_id = take_u32(&mut b)?;
Some((lsp_id, stream, update_id))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LspQueryRequest<'a> {
pub nonce: u16,
pub lsp_id: u16,
pub kind: u8,
pub flags: u8,
pub line: u32,
pub col: u32,
pub path: &'a str,
pub arg: &'a str,
}
pub fn msg_lsp_query(req: &LspQueryRequest<'_>) -> Vec<u8> {
let mut msg = Vec::with_capacity(19 + req.path.len() + req.arg.len());
msg.push(C2S_LSP_QUERY);
msg.extend_from_slice(&req.nonce.to_le_bytes());
msg.extend_from_slice(&req.lsp_id.to_le_bytes());
msg.push(req.kind);
msg.push(req.flags);
msg.extend_from_slice(&req.line.to_le_bytes());
msg.extend_from_slice(&req.col.to_le_bytes());
push_str(&mut msg, req.path);
push_str(&mut msg, req.arg);
msg
}
pub fn parse_lsp_query(msg: &[u8]) -> Option<LspQueryRequest<'_>> {
let mut b = body_of(msg, C2S_LSP_QUERY)?;
let nonce = take_u16(&mut b)?;
let lsp_id = take_u16(&mut b)?;
let kind = take_u8(&mut b)?;
let flags = take_u8(&mut b)?;
let line = take_u32(&mut b)?;
let col = take_u32(&mut b)?;
let path = take_str(&mut b)?;
let arg = take_str(&mut b)?;
Some(LspQueryRequest {
nonce,
lsp_id,
kind,
flags,
line,
col,
path,
arg,
})
}
pub fn msg_lsp_cancel(nonce: u16) -> Vec<u8> {
let mut msg = Vec::with_capacity(3);
msg.push(C2S_LSP_CANCEL);
msg.extend_from_slice(&nonce.to_le_bytes());
msg
}
pub fn parse_lsp_cancel(msg: &[u8]) -> Option<u16> {
let mut b = body_of(msg, C2S_LSP_CANCEL)?;
take_u16(&mut b)
}
pub fn msg_lsp_servers(nonce: u16) -> Vec<u8> {
let mut msg = Vec::with_capacity(3);
msg.push(C2S_LSP_SERVERS);
msg.extend_from_slice(&nonce.to_le_bytes());
msg
}
pub fn parse_lsp_servers(msg: &[u8]) -> Option<u16> {
let mut b = body_of(msg, C2S_LSP_SERVERS)?;
take_u16(&mut b)
}
pub fn msg_lsp_stop(nonce: u16, server_ref: u16) -> Vec<u8> {
let mut msg = Vec::with_capacity(5);
msg.push(C2S_LSP_STOP);
msg.extend_from_slice(&nonce.to_le_bytes());
msg.extend_from_slice(&server_ref.to_le_bytes());
msg
}
pub fn parse_lsp_stop(msg: &[u8]) -> Option<(u16, u16)> {
let mut b = body_of(msg, C2S_LSP_STOP)?;
let nonce = take_u16(&mut b)?;
let server_ref = take_u16(&mut b)?;
Some((nonce, server_ref))
}
pub fn msg_lsp_buffer(lsp_id: u16, flags: u8, path: &str, text: &[u8]) -> Vec<u8> {
let compressed = lz4_flex::compress_prepend_size(text);
let mut msg = Vec::with_capacity(6 + path.len() + compressed.len());
msg.push(C2S_LSP_BUFFER);
msg.extend_from_slice(&lsp_id.to_le_bytes());
msg.push(flags);
push_str(&mut msg, path);
msg.extend_from_slice(&compressed);
msg
}
pub fn parse_lsp_buffer(msg: &[u8]) -> Option<(u16, u8, &str, Vec<u8>)> {
let mut b = body_of(msg, C2S_LSP_BUFFER)?;
let lsp_id = take_u16(&mut b)?;
let flags = take_u8(&mut b)?;
let path = take_str(&mut b)?;
let text = decompress_guarded(b)?;
Some((lsp_id, flags, path, text))
}
pub fn msg_lsp_opened(
nonce: u16,
lsp_id: u16,
status: u8,
flags: u8,
root: &str,
detail: &str,
) -> Vec<u8> {
let mut msg = Vec::with_capacity(11 + root.len() + detail.len());
msg.push(S2C_LSP_OPENED);
msg.extend_from_slice(&nonce.to_le_bytes());
msg.extend_from_slice(&lsp_id.to_le_bytes());
msg.push(status);
msg.push(flags);
push_str(&mut msg, root);
push_str(&mut msg, detail);
msg
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LspOpened<'a> {
pub nonce: u16,
pub lsp_id: u16,
pub status: u8,
pub flags: u8,
pub root: &'a str,
pub detail: &'a str,
}
pub fn parse_lsp_opened(msg: &[u8]) -> Option<LspOpened<'_>> {
let mut b = body_of(msg, S2C_LSP_OPENED)?;
let nonce = take_u16(&mut b)?;
let lsp_id = take_u16(&mut b)?;
let status = take_u8(&mut b)?;
let flags = take_u8(&mut b)?;
let root = take_str(&mut b)?;
let detail = take_str(&mut b)?;
Some(LspOpened {
nonce,
lsp_id,
status,
flags,
root,
detail,
})
}
pub fn msg_lsp_state(lsp_id: u16, state_id: u32, flags: u8, records: &[u8]) -> Vec<u8> {
let compressed = lz4_flex::compress_prepend_size(records);
let mut msg = Vec::with_capacity(8 + compressed.len());
msg.push(S2C_LSP_STATE);
msg.extend_from_slice(&lsp_id.to_le_bytes());
msg.extend_from_slice(&state_id.to_le_bytes());
msg.push(flags);
msg.extend_from_slice(&compressed);
msg
}
pub fn parse_lsp_state(msg: &[u8]) -> Option<(u16, u32, u8, Vec<u8>)> {
let mut b = body_of(msg, S2C_LSP_STATE)?;
let lsp_id = take_u16(&mut b)?;
let state_id = take_u32(&mut b)?;
let flags = take_u8(&mut b)?;
let records = decompress_guarded(b)?;
Some((lsp_id, state_id, flags, records))
}
pub fn msg_lsp_diag(lsp_id: u16, update_id: u32, flags: u8, records: &[u8]) -> Vec<u8> {
let compressed = lz4_flex::compress_prepend_size(records);
let mut msg = Vec::with_capacity(8 + compressed.len());
msg.push(S2C_LSP_DIAG);
msg.extend_from_slice(&lsp_id.to_le_bytes());
msg.extend_from_slice(&update_id.to_le_bytes());
msg.push(flags);
msg.extend_from_slice(&compressed);
msg
}
pub fn parse_lsp_diag(msg: &[u8]) -> Option<(u16, u32, u8, Vec<u8>)> {
let mut b = body_of(msg, S2C_LSP_DIAG)?;
let lsp_id = take_u16(&mut b)?;
let update_id = take_u32(&mut b)?;
let flags = take_u8(&mut b)?;
let records = decompress_guarded(b)?;
Some((lsp_id, update_id, flags, records))
}
pub fn msg_lsp_query_resp(
nonce: u16,
status: u8,
flags: u8,
detail: &str,
records: &[u8],
) -> Vec<u8> {
let compressed = lz4_flex::compress_prepend_size(records);
let mut msg = Vec::with_capacity(7 + detail.len() + compressed.len());
msg.push(S2C_LSP_QUERY);
msg.extend_from_slice(&nonce.to_le_bytes());
msg.push(status);
msg.push(flags);
push_str(&mut msg, detail);
msg.extend_from_slice(&compressed);
msg
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LspQueryResp {
pub nonce: u16,
pub status: u8,
pub flags: u8,
pub detail: String,
pub records: Vec<u8>,
}
pub fn parse_lsp_query_resp(msg: &[u8]) -> Option<LspQueryResp> {
let mut b = body_of(msg, S2C_LSP_QUERY)?;
let nonce = take_u16(&mut b)?;
let status = take_u8(&mut b)?;
let flags = take_u8(&mut b)?;
let detail = take_str(&mut b)?.to_string();
let records = decompress_guarded(b)?;
Some(LspQueryResp {
nonce,
status,
flags,
detail,
records,
})
}
pub fn msg_lsp_closed(lsp_id: u16, reason: u8) -> Vec<u8> {
let mut msg = Vec::with_capacity(4);
msg.push(S2C_LSP_CLOSED);
msg.extend_from_slice(&lsp_id.to_le_bytes());
msg.push(reason);
msg
}
pub fn parse_lsp_closed(msg: &[u8]) -> Option<(u16, u8)> {
let mut b = body_of(msg, S2C_LSP_CLOSED)?;
let lsp_id = take_u16(&mut b)?;
let reason = take_u8(&mut b)?;
Some((lsp_id, reason))
}
pub fn msg_lsp_servers_resp(nonce: u16, status: u8, flags: u8, records: &[u8]) -> Vec<u8> {
let compressed = lz4_flex::compress_prepend_size(records);
let mut msg = Vec::with_capacity(5 + compressed.len());
msg.push(S2C_LSP_SERVERS);
msg.extend_from_slice(&nonce.to_le_bytes());
msg.push(status);
msg.push(flags);
msg.extend_from_slice(&compressed);
msg
}
pub fn parse_lsp_servers_resp(msg: &[u8]) -> Option<(u16, u8, u8, Vec<u8>)> {
let mut b = body_of(msg, S2C_LSP_SERVERS)?;
let nonce = take_u16(&mut b)?;
let status = take_u8(&mut b)?;
let flags = take_u8(&mut b)?;
let records = decompress_guarded(b)?;
Some((nonce, status, flags, records))
}
pub fn msg_lsp_stopped(nonce: u16, status: u8) -> Vec<u8> {
let mut msg = Vec::with_capacity(4);
msg.push(S2C_LSP_STOPPED);
msg.extend_from_slice(&nonce.to_le_bytes());
msg.push(status);
msg
}
pub fn parse_lsp_stopped(msg: &[u8]) -> Option<(u16, u8)> {
let mut b = body_of(msg, S2C_LSP_STOPPED)?;
let nonce = take_u16(&mut b)?;
let status = take_u8(&mut b)?;
Some((nonce, status))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LspStateRecord<'a> {
Server {
server_ref: u16,
phase: u8,
progress_pct: u8,
caps: u32,
epoch: u32,
refused_edits: u32,
rss: u64,
id: &'a str,
msg: &'a str,
},
}
pub fn append_lsp_state_record(buf: &mut Vec<u8>, record: &LspStateRecord<'_>) {
let start = begin_record(buf);
match record {
LspStateRecord::Server {
server_ref,
phase,
progress_pct,
caps,
epoch,
refused_edits,
rss,
id,
msg,
} => {
buf.push(LSP_STATE_RECORD_SERVER);
buf.extend_from_slice(&server_ref.to_le_bytes());
buf.push(*phase);
buf.push(*progress_pct);
buf.extend_from_slice(&caps.to_le_bytes());
buf.extend_from_slice(&epoch.to_le_bytes());
buf.extend_from_slice(&refused_edits.to_le_bytes());
buf.extend_from_slice(&rss.to_le_bytes());
push_str(buf, id);
push_str(buf, msg);
}
}
end_record(buf, start);
}
pub struct LspStateRecordIter<'a> {
data: &'a [u8],
}
pub fn lsp_state_records(data: &[u8]) -> LspStateRecordIter<'_> {
LspStateRecordIter { data }
}
impl<'a> Iterator for LspStateRecordIter<'a> {
type Item = LspStateRecord<'a>;
fn next(&mut self) -> Option<LspStateRecord<'a>> {
loop {
let (kind, mut b) = next_record(&mut self.data)?;
match kind {
LSP_STATE_RECORD_SERVER => {
let server_ref = take_u16(&mut b)?;
let phase = take_u8(&mut b)?;
let progress_pct = take_u8(&mut b)?;
let caps = take_u32(&mut b)?;
let epoch = take_u32(&mut b)?;
let refused_edits = take_u32(&mut b)?;
let rss = take_u64(&mut b)?;
let id = take_str(&mut b)?;
let msg = take_str(&mut b)?;
return Some(LspStateRecord::Server {
server_ref,
phase,
progress_pct,
caps,
epoch,
refused_edits,
rss,
id,
msg,
});
}
_ => continue, }
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LspServersRecord<'a> {
Server {
server_ref: u16,
phase: u8,
progress_pct: u8,
caps: u32,
epoch: u32,
refused_edits: u32,
rss: u64,
id: &'a str,
msg: &'a str,
root: &'a str,
},
}
pub fn append_lsp_servers_record(buf: &mut Vec<u8>, record: &LspServersRecord<'_>) {
let start = begin_record(buf);
match record {
LspServersRecord::Server {
server_ref,
phase,
progress_pct,
caps,
epoch,
refused_edits,
rss,
id,
msg,
root,
} => {
buf.push(LSP_STATE_RECORD_SERVER);
buf.extend_from_slice(&server_ref.to_le_bytes());
buf.push(*phase);
buf.push(*progress_pct);
buf.extend_from_slice(&caps.to_le_bytes());
buf.extend_from_slice(&epoch.to_le_bytes());
buf.extend_from_slice(&refused_edits.to_le_bytes());
buf.extend_from_slice(&rss.to_le_bytes());
push_str(buf, id);
push_str(buf, msg);
push_str(buf, root);
}
}
end_record(buf, start);
}
pub struct LspServersRecordIter<'a> {
data: &'a [u8],
}
pub fn lsp_servers_records(data: &[u8]) -> LspServersRecordIter<'_> {
LspServersRecordIter { data }
}
impl<'a> Iterator for LspServersRecordIter<'a> {
type Item = LspServersRecord<'a>;
fn next(&mut self) -> Option<LspServersRecord<'a>> {
loop {
let (kind, mut b) = next_record(&mut self.data)?;
match kind {
LSP_STATE_RECORD_SERVER => {
let server_ref = take_u16(&mut b)?;
let phase = take_u8(&mut b)?;
let progress_pct = take_u8(&mut b)?;
let caps = take_u32(&mut b)?;
let epoch = take_u32(&mut b)?;
let refused_edits = take_u32(&mut b)?;
let rss = take_u64(&mut b)?;
let id = take_str(&mut b)?;
let msg = take_str(&mut b)?;
let root = take_str(&mut b)?;
return Some(LspServersRecord::Server {
server_ref,
phase,
progress_pct,
caps,
epoch,
refused_edits,
rss,
id,
msg,
root,
});
}
_ => continue, }
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LspDiagRecord<'a> {
File {
hash: LspHash,
n: u16,
path: &'a str,
},
Diag {
severity: u8,
flags: u8,
line: u32,
col: u32,
end_line: u32,
end_col: u32,
code: &'a str,
source: &'a str,
msg: &'a str,
},
}
pub fn append_lsp_diag_record(buf: &mut Vec<u8>, record: &LspDiagRecord<'_>) {
let start = begin_record(buf);
match record {
LspDiagRecord::File { hash, n, path } => {
buf.push(LSP_DIAG_RECORD_FILE);
buf.extend_from_slice(hash);
buf.extend_from_slice(&n.to_le_bytes());
push_str(buf, path);
}
LspDiagRecord::Diag {
severity,
flags,
line,
col,
end_line,
end_col,
code,
source,
msg,
} => {
buf.push(LSP_DIAG_RECORD_DIAG);
buf.push(*severity);
buf.push(*flags);
buf.extend_from_slice(&line.to_le_bytes());
buf.extend_from_slice(&col.to_le_bytes());
buf.extend_from_slice(&end_line.to_le_bytes());
buf.extend_from_slice(&end_col.to_le_bytes());
push_str(buf, code);
push_str(buf, source);
push_bytes(buf, msg.as_bytes());
}
}
end_record(buf, start);
}
pub struct LspDiagRecordIter<'a> {
data: &'a [u8],
}
pub fn lsp_diag_records(data: &[u8]) -> LspDiagRecordIter<'_> {
LspDiagRecordIter { data }
}
impl<'a> Iterator for LspDiagRecordIter<'a> {
type Item = LspDiagRecord<'a>;
fn next(&mut self) -> Option<LspDiagRecord<'a>> {
loop {
let (kind, mut b) = next_record(&mut self.data)?;
match kind {
LSP_DIAG_RECORD_FILE => {
let hash = take_hash(&mut b)?;
let n = take_u16(&mut b)?;
let path = take_str(&mut b)?;
return Some(LspDiagRecord::File { hash, n, path });
}
LSP_DIAG_RECORD_DIAG => {
let severity = take_u8(&mut b)?;
let flags = take_u8(&mut b)?;
let line = take_u32(&mut b)?;
let col = take_u32(&mut b)?;
let end_line = take_u32(&mut b)?;
let end_col = take_u32(&mut b)?;
let code = take_str(&mut b)?;
let source = take_str(&mut b)?;
let msg = take_text(&mut b)?;
return Some(LspDiagRecord::Diag {
severity,
flags,
line,
col,
end_line,
end_col,
code,
source,
msg,
});
}
_ => continue, }
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LspQueryRecord<'a> {
Location {
flags: u8,
hash: LspHash,
line: u32,
col: u32,
end_line: u32,
end_col: u32,
path: &'a str,
},
Markup {
format: u8,
text: &'a str,
},
Symbol {
sym_kind: u8,
flags: u8,
depth: u8,
line: u32,
col: u32,
end_line: u32,
end_col: u32,
name: &'a str,
path: &'a str,
},
Edit {
flags: u8,
hash: LspHash,
line: u32,
col: u32,
end_line: u32,
end_col: u32,
new_text: &'a str,
path: &'a str,
},
Completion {
item_kind: u8,
flags: u8,
line: u32,
col: u32,
end_line: u32,
end_col: u32,
label: &'a str,
insert: &'a str,
detail: &'a str,
},
Signature {
flags: u8,
active_param: u16,
param_start: u16,
param_end: u16,
label: &'a str,
doc: &'a str,
},
}
pub fn append_lsp_query_record(buf: &mut Vec<u8>, record: &LspQueryRecord<'_>) {
let start = begin_record(buf);
match record {
LspQueryRecord::Location {
flags,
hash,
line,
col,
end_line,
end_col,
path,
} => {
buf.push(LSP_QUERY_RECORD_LOCATION);
buf.push(*flags);
buf.extend_from_slice(hash);
buf.extend_from_slice(&line.to_le_bytes());
buf.extend_from_slice(&col.to_le_bytes());
buf.extend_from_slice(&end_line.to_le_bytes());
buf.extend_from_slice(&end_col.to_le_bytes());
push_str(buf, path);
}
LspQueryRecord::Markup { format, text } => {
buf.push(LSP_QUERY_RECORD_MARKUP);
buf.push(*format);
push_bytes(buf, text.as_bytes());
}
LspQueryRecord::Symbol {
sym_kind,
flags,
depth,
line,
col,
end_line,
end_col,
name,
path,
} => {
buf.push(LSP_QUERY_RECORD_SYMBOL);
buf.push(*sym_kind);
buf.push(*flags);
buf.push(*depth);
buf.extend_from_slice(&line.to_le_bytes());
buf.extend_from_slice(&col.to_le_bytes());
buf.extend_from_slice(&end_line.to_le_bytes());
buf.extend_from_slice(&end_col.to_le_bytes());
push_str(buf, name);
push_str(buf, path);
}
LspQueryRecord::Edit {
flags,
hash,
line,
col,
end_line,
end_col,
new_text,
path,
} => {
buf.push(LSP_QUERY_RECORD_EDIT);
buf.push(*flags);
buf.extend_from_slice(hash);
buf.extend_from_slice(&line.to_le_bytes());
buf.extend_from_slice(&col.to_le_bytes());
buf.extend_from_slice(&end_line.to_le_bytes());
buf.extend_from_slice(&end_col.to_le_bytes());
push_bytes(buf, new_text.as_bytes());
push_str(buf, path);
}
LspQueryRecord::Completion {
item_kind,
flags,
line,
col,
end_line,
end_col,
label,
insert,
detail,
} => {
buf.push(LSP_QUERY_RECORD_COMPLETION);
buf.push(*item_kind);
buf.push(*flags);
buf.extend_from_slice(&line.to_le_bytes());
buf.extend_from_slice(&col.to_le_bytes());
buf.extend_from_slice(&end_line.to_le_bytes());
buf.extend_from_slice(&end_col.to_le_bytes());
push_str(buf, label);
push_str(buf, insert);
push_str(buf, detail);
}
LspQueryRecord::Signature {
flags,
active_param,
param_start,
param_end,
label,
doc,
} => {
buf.push(LSP_QUERY_RECORD_SIGNATURE);
buf.push(*flags);
buf.extend_from_slice(&active_param.to_le_bytes());
buf.extend_from_slice(¶m_start.to_le_bytes());
buf.extend_from_slice(¶m_end.to_le_bytes());
push_str(buf, label);
push_bytes(buf, doc.as_bytes());
}
}
end_record(buf, start);
}
pub struct LspQueryRecordIter<'a> {
data: &'a [u8],
}
pub fn lsp_query_records(data: &[u8]) -> LspQueryRecordIter<'_> {
LspQueryRecordIter { data }
}
impl<'a> Iterator for LspQueryRecordIter<'a> {
type Item = LspQueryRecord<'a>;
fn next(&mut self) -> Option<LspQueryRecord<'a>> {
loop {
let (kind, mut b) = next_record(&mut self.data)?;
match kind {
LSP_QUERY_RECORD_LOCATION => {
let flags = take_u8(&mut b)?;
let hash = take_hash(&mut b)?;
let line = take_u32(&mut b)?;
let col = take_u32(&mut b)?;
let end_line = take_u32(&mut b)?;
let end_col = take_u32(&mut b)?;
let path = take_str(&mut b)?;
return Some(LspQueryRecord::Location {
flags,
hash,
line,
col,
end_line,
end_col,
path,
});
}
LSP_QUERY_RECORD_MARKUP => {
let format = take_u8(&mut b)?;
let text = take_text(&mut b)?;
return Some(LspQueryRecord::Markup { format, text });
}
LSP_QUERY_RECORD_SYMBOL => {
let sym_kind = take_u8(&mut b)?;
let flags = take_u8(&mut b)?;
let depth = take_u8(&mut b)?;
let line = take_u32(&mut b)?;
let col = take_u32(&mut b)?;
let end_line = take_u32(&mut b)?;
let end_col = take_u32(&mut b)?;
let name = take_str(&mut b)?;
let path = take_str(&mut b)?;
return Some(LspQueryRecord::Symbol {
sym_kind,
flags,
depth,
line,
col,
end_line,
end_col,
name,
path,
});
}
LSP_QUERY_RECORD_EDIT => {
let flags = take_u8(&mut b)?;
let hash = take_hash(&mut b)?;
let line = take_u32(&mut b)?;
let col = take_u32(&mut b)?;
let end_line = take_u32(&mut b)?;
let end_col = take_u32(&mut b)?;
let new_text = take_text(&mut b)?;
let path = take_str(&mut b)?;
return Some(LspQueryRecord::Edit {
flags,
hash,
line,
col,
end_line,
end_col,
new_text,
path,
});
}
LSP_QUERY_RECORD_COMPLETION => {
let item_kind = take_u8(&mut b)?;
let flags = take_u8(&mut b)?;
let line = take_u32(&mut b)?;
let col = take_u32(&mut b)?;
let end_line = take_u32(&mut b)?;
let end_col = take_u32(&mut b)?;
let label = take_str(&mut b)?;
let insert = take_str(&mut b)?;
let detail = take_str(&mut b)?;
return Some(LspQueryRecord::Completion {
item_kind,
flags,
line,
col,
end_line,
end_col,
label,
insert,
detail,
});
}
LSP_QUERY_RECORD_SIGNATURE => {
let flags = take_u8(&mut b)?;
let active_param = take_u16(&mut b)?;
let param_start = take_u16(&mut b)?;
let param_end = take_u16(&mut b)?;
let label = take_str(&mut b)?;
let doc = take_text(&mut b)?;
return Some(LspQueryRecord::Signature {
flags,
active_param,
param_start,
param_end,
label,
doc,
});
}
_ => continue, }
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LspServerState {
pub phase: u8,
pub progress_pct: u8,
pub caps: u32,
pub epoch: u32,
pub refused_edits: u32,
pub rss: u64,
pub id: String,
pub msg: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LspStateMirror {
pub servers: BTreeMap<u16, LspServerState>,
pub flags: u8,
}
impl LspStateMirror {
pub fn new() -> Self {
Self::default()
}
pub fn apply_state(&mut self, msg: &[u8]) -> Option<u32> {
let (_lsp_id, state_id, flags, records) = parse_lsp_state(msg)?;
let mut next = LspStateMirror {
flags,
..Default::default()
};
for record in lsp_state_records(&records) {
match record {
LspStateRecord::Server {
server_ref,
phase,
progress_pct,
caps,
epoch,
refused_edits,
rss,
id,
msg,
} => {
next.servers.insert(
server_ref,
LspServerState {
phase,
progress_pct,
caps,
epoch,
refused_edits,
rss,
id: id.to_string(),
msg: msg.to_string(),
},
);
}
}
}
*self = next;
Some(state_id)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LspDiagnostic {
pub severity: u8,
pub flags: u8,
pub line: u32,
pub col: u32,
pub end_line: u32,
pub end_col: u32,
pub code: String,
pub source: String,
pub msg: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LspFileDiags {
pub hash: LspHash,
pub diags: Vec<LspDiagnostic>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LspDiagMirror {
pub files: BTreeMap<String, LspFileDiags>,
}
impl LspDiagMirror {
pub fn new() -> Self {
Self::default()
}
pub fn apply_diag(&mut self, msg: &[u8]) -> Option<u32> {
let (_lsp_id, update_id, flags, records) = parse_lsp_diag(msg)?;
if flags & LSP_DIAG_FULL != 0 {
self.files.clear();
}
let mut current: Option<String> = None;
for record in lsp_diag_records(&records) {
match record {
LspDiagRecord::File { hash, n, path } => {
if n == 0 {
self.files.remove(path);
current = None;
} else {
let entry = self.files.entry(path.to_string()).or_default();
entry.hash = hash;
entry.diags.clear();
current = Some(path.to_string());
}
}
LspDiagRecord::Diag {
severity,
flags,
line,
col,
end_line,
end_col,
code,
source,
msg,
} => {
if let Some(file) = current.as_ref().and_then(|p| self.files.get_mut(p)) {
file.diags.push(LspDiagnostic {
severity,
flags,
line,
col,
end_line,
end_col,
code: code.to_string(),
source: source.to_string(),
msg: msg.to_string(),
});
}
}
}
}
Some(update_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lsp_open_from_pty_roundtrip_and_rebase() {
let m = msg_lsp_open_from_pty(7, LSP_OPEN_WATCH, 500, "sub", 3);
assert_eq!(
m,
vec![
0x60, 0x07, 0x00, 0x05, 0xf4, 0x01, 0x03, 0x00, 0x73, 0x75, 0x62, 0x03, 0x00
]
);
assert_eq!(lsp_open_src_pty(&m), Some(3));
assert_eq!(
lsp_open_src_pty(&msg_lsp_open(7, LSP_OPEN_WATCH, 500, "sub")),
None
);
let (_, flags, _, path) = parse_lsp_open(&m).unwrap();
assert_eq!(flags & LSP_OPEN_FROM_PTY, LSP_OPEN_FROM_PTY);
assert_eq!(path, "sub");
let reb = lsp_open_rebase(&m, Some("/w")).unwrap();
let (_, rf, _, rp) = parse_lsp_open(&reb).unwrap();
assert_eq!(rf & LSP_OPEN_FROM_PTY, 0);
assert_eq!(rp, "/w/sub");
}
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
fn hash(fill: u8) -> LspHash {
[fill; 16]
}
#[test]
fn request_roundtrips() {
let msg = msg_lsp_open(7, LSP_OPEN_WATCH | LSP_OPEN_DIAGS, 500, "/src");
assert_eq!(parse_lsp_open(&msg), Some((7, 3, 500, "/src")));
let msg = msg_lsp_close(9);
assert_eq!(parse_lsp_close(&msg), Some(9));
let msg = msg_lsp_ack(9, LSP_STREAM_DIAG, 0x01020304);
assert_eq!(parse_lsp_ack(&msg), Some((9, LSP_STREAM_DIAG, 0x01020304)));
let req = LspQueryRequest {
nonce: 3,
lsp_id: 9,
kind: LSP_QUERY_REFERENCES,
flags: LSP_REFS_INCLUDE_DECLARATION,
line: 10,
col: 4,
path: "src/lib.rs",
arg: "",
};
assert_eq!(parse_lsp_query(&msg_lsp_query(&req)), Some(req));
let msg = msg_lsp_cancel(3);
assert_eq!(parse_lsp_cancel(&msg), Some(3));
let msg = msg_lsp_servers(4);
assert_eq!(parse_lsp_servers(&msg), Some(4));
let msg = msg_lsp_stop(5, 2);
assert_eq!(parse_lsp_stop(&msg), Some((5, 2)));
let msg = msg_lsp_buffer(9, 0, "src/lib.rs", b"fn main() {}\n");
assert_eq!(
parse_lsp_buffer(&msg),
Some((9, 0, "src/lib.rs", b"fn main() {}\n".to_vec()))
);
let msg = msg_lsp_buffer(9, LSP_BUFFER_RELEASE, "src/lib.rs", b"");
assert_eq!(
parse_lsp_buffer(&msg),
Some((9, LSP_BUFFER_RELEASE, "src/lib.rs", Vec::new()))
);
}
#[test]
fn response_roundtrips() {
let msg = msg_lsp_opened(7, 1, LSP_STATUS_OK, 0, "/src", "");
assert_eq!(
parse_lsp_opened(&msg),
Some(LspOpened {
nonce: 7,
lsp_id: 1,
status: LSP_STATUS_OK,
flags: 0,
root: "/src",
detail: "",
})
);
let msg = msg_lsp_opened(
8,
LSP_ID_INVALID,
LSP_STATUS_NOT_FOUND,
0,
"",
"gopls: not found on PATH",
);
let opened = parse_lsp_opened(&msg).unwrap();
assert_eq!(opened.lsp_id, LSP_ID_INVALID);
assert_eq!(opened.detail, "gopls: not found on PATH");
let mut records = Vec::new();
append_lsp_state_record(
&mut records,
&LspStateRecord::Server {
server_ref: 1,
phase: LSP_PHASE_INDEXING,
progress_pct: 42,
caps: LSP_CAP_DEFINITION | LSP_CAP_HOVER,
epoch: 2,
refused_edits: 1,
rss: 3 << 30,
id: "rust-analyzer",
msg: "indexing 42%",
},
);
let msg = msg_lsp_state(1, 5, 0, &records);
let (lsp_id, state_id, flags, decoded) = parse_lsp_state(&msg).unwrap();
assert_eq!((lsp_id, state_id, flags), (1, 5, 0));
assert_eq!(decoded, records);
let msg = msg_lsp_diag(1, 6, LSP_DIAG_FULL, &[]);
assert_eq!(parse_lsp_diag(&msg), Some((1, 6, LSP_DIAG_FULL, vec![])));
let msg = msg_lsp_query_resp(3, LSP_STATUS_OK, 0, "", &[]);
assert_eq!(
parse_lsp_query_resp(&msg),
Some(LspQueryResp {
nonce: 3,
status: LSP_STATUS_OK,
flags: 0,
detail: String::new(),
records: vec![],
})
);
let msg = msg_lsp_query_resp(4, LSP_STATUS_OTHER, 0, "gopls: boom", &[]);
let resp = parse_lsp_query_resp(&msg).unwrap();
assert_eq!(resp.status, LSP_STATUS_OTHER);
assert_eq!(resp.detail, "gopls: boom");
let msg = msg_lsp_closed(1, LSP_CLOSED_BACKEND_FAILED);
assert_eq!(parse_lsp_closed(&msg), Some((1, LSP_CLOSED_BACKEND_FAILED)));
let msg = msg_lsp_servers_resp(4, LSP_STATUS_OK, 0, &[]);
assert_eq!(
parse_lsp_servers_resp(&msg),
Some((4, LSP_STATUS_OK, 0, vec![]))
);
let msg = msg_lsp_stopped(5, LSP_STATUS_OK);
assert_eq!(parse_lsp_stopped(&msg), Some((5, LSP_STATUS_OK)));
}
#[test]
fn state_record_roundtrip() {
let record = LspStateRecord::Server {
server_ref: 2,
phase: LSP_PHASE_READY,
progress_pct: LSP_PROGRESS_UNKNOWN,
caps: LSP_CAP_RENAME,
epoch: 0,
refused_edits: 0,
rss: 0,
id: "gopls",
msg: "",
};
let mut buf = Vec::new();
append_lsp_state_record(&mut buf, &record);
let decoded: Vec<_> = lsp_state_records(&buf).collect();
assert_eq!(decoded, vec![record]);
}
#[test]
fn servers_record_roundtrip() {
let record = LspServersRecord::Server {
server_ref: 2,
phase: LSP_PHASE_READY,
progress_pct: 100,
caps: 0x3F,
epoch: 1,
refused_edits: 0,
rss: 1 << 20,
id: "gopls",
msg: "ready",
root: "/work/api",
};
let mut buf = Vec::new();
append_lsp_servers_record(&mut buf, &record);
let decoded: Vec<_> = lsp_servers_records(&buf).collect();
assert_eq!(decoded, vec![record]);
}
#[test]
fn diag_record_roundtrip() {
let records = vec![
LspDiagRecord::File {
hash: hash(0xAB),
n: 1,
path: "src/lib.rs",
},
LspDiagRecord::Diag {
severity: LSP_SEVERITY_ERROR,
flags: LSP_DIAG_UNNECESSARY,
line: 10,
col: 4,
end_line: 10,
end_col: 9,
code: "E0308",
source: "rustc",
msg: "mismatched types",
},
LspDiagRecord::File {
hash: LSP_HASH_NONE,
n: 0,
path: "src/old.rs",
},
];
let mut buf = Vec::new();
for r in &records {
append_lsp_diag_record(&mut buf, r);
}
let decoded: Vec<_> = lsp_diag_records(&buf).collect();
assert_eq!(decoded, records);
}
#[test]
fn query_record_roundtrip() {
let records = vec![
LspQueryRecord::Location {
flags: 0,
hash: hash(0xCD),
line: 5,
col: 0,
end_line: 5,
end_col: 12,
path: "src/main.rs",
},
LspQueryRecord::Markup {
format: LSP_MARKUP_MARKDOWN,
text: "```rust\nfn main()\n```",
},
LspQueryRecord::Symbol {
sym_kind: 12, flags: LSP_SYMBOL_DEPRECATED,
depth: 1,
line: 3,
col: 4,
end_line: 9,
end_col: 1,
name: "main",
path: "src/main.rs",
},
LspQueryRecord::Edit {
flags: 0,
hash: hash(0xEF),
line: 3,
col: 7,
end_line: 3,
end_col: 11,
new_text: "run",
path: "src/main.rs",
},
LspQueryRecord::Completion {
item_kind: 3, flags: LSP_COMPLETION_SNIPPET | LSP_COMPLETION_PRESELECT,
line: 4,
col: 2,
end_line: 4,
end_col: 6,
label: "push",
insert: "push(${1:value})",
detail: "fn(&mut self, value: T)",
},
LspQueryRecord::Signature {
flags: LSP_SIGNATURE_ACTIVE,
active_param: 1,
param_start: 14,
param_end: 22,
label: "fn get(&self, index: usize)",
doc: "Returns a reference to an element.",
},
];
let mut buf = Vec::new();
for r in &records {
append_lsp_query_record(&mut buf, r);
}
let decoded: Vec<_> = lsp_query_records(&buf).collect();
assert_eq!(decoded, records);
}
#[test]
fn unknown_record_kind_is_skipped() {
let mut buf = Vec::new();
let start = begin_record(&mut buf);
buf.push(0x7F);
buf.extend_from_slice(b"future");
end_record(&mut buf, start);
append_lsp_query_record(
&mut buf,
&LspQueryRecord::Markup {
format: LSP_MARKUP_PLAIN,
text: "hi",
},
);
let decoded: Vec<_> = lsp_query_records(&buf).collect();
assert_eq!(
decoded,
vec![LspQueryRecord::Markup {
format: LSP_MARKUP_PLAIN,
text: "hi",
}]
);
}
#[test]
fn malformed_record_ends_iteration() {
let mut buf = Vec::new();
append_lsp_diag_record(
&mut buf,
&LspDiagRecord::File {
hash: LSP_HASH_NONE,
n: 0,
path: "a",
},
);
buf.extend_from_slice(&99u32.to_le_bytes());
buf.push(LSP_DIAG_RECORD_FILE);
let decoded: Vec<_> = lsp_diag_records(&buf).collect();
assert_eq!(decoded.len(), 1);
}
#[test]
fn oversized_declared_length_is_rejected_before_allocation() {
let mut msg = vec![S2C_LSP_STATE];
msg.extend_from_slice(&1u16.to_le_bytes());
msg.extend_from_slice(&1u32.to_le_bytes());
msg.push(0);
msg.extend_from_slice(&(u32::MAX).to_le_bytes());
msg.extend_from_slice(&[0; 8]);
assert_eq!(parse_lsp_state(&msg), None);
}
#[test]
fn state_mirror_replaces_whole_state() {
let mut records = Vec::new();
append_lsp_state_record(
&mut records,
&LspStateRecord::Server {
server_ref: 1,
phase: LSP_PHASE_INDEXING,
progress_pct: 10,
caps: 0,
epoch: 0,
refused_edits: 0,
rss: 0,
id: "rust-analyzer",
msg: "indexing",
},
);
let mut mirror = LspStateMirror::new();
assert_eq!(
mirror.apply_state(&msg_lsp_state(1, 1, 0, &records)),
Some(1)
);
assert_eq!(mirror.servers.len(), 1);
assert_eq!(mirror.servers[&1].phase, LSP_PHASE_INDEXING);
let mut records = Vec::new();
append_lsp_state_record(
&mut records,
&LspStateRecord::Server {
server_ref: 2,
phase: LSP_PHASE_READY,
progress_pct: 100,
caps: 0x3F,
epoch: 0,
refused_edits: 0,
rss: 0,
id: "gopls",
msg: "",
},
);
assert_eq!(
mirror.apply_state(&msg_lsp_state(1, 2, 0, &records)),
Some(2)
);
assert_eq!(mirror.servers.len(), 1);
assert!(mirror.servers.contains_key(&2));
}
#[test]
fn diag_mirror_applies_replacement_sets() {
let mut mirror = LspDiagMirror::new();
let mut records = Vec::new();
append_lsp_diag_record(
&mut records,
&LspDiagRecord::File {
hash: hash(1),
n: 1,
path: "a.rs",
},
);
append_lsp_diag_record(
&mut records,
&LspDiagRecord::Diag {
severity: LSP_SEVERITY_ERROR,
flags: 0,
line: 1,
col: 0,
end_line: 1,
end_col: 5,
code: "E1",
source: "t",
msg: "boom",
},
);
assert_eq!(
mirror.apply_diag(&msg_lsp_diag(1, 1, LSP_DIAG_FULL, &records)),
Some(1)
);
assert_eq!(mirror.files.len(), 1);
assert_eq!(mirror.files["a.rs"].diags.len(), 1);
let mut records = Vec::new();
append_lsp_diag_record(
&mut records,
&LspDiagRecord::File {
hash: hash(2),
n: 0,
path: "a.rs",
},
);
append_lsp_diag_record(
&mut records,
&LspDiagRecord::File {
hash: hash(3),
n: 1,
path: "b.rs",
},
);
append_lsp_diag_record(
&mut records,
&LspDiagRecord::Diag {
severity: LSP_SEVERITY_WARNING,
flags: 0,
line: 2,
col: 1,
end_line: 2,
end_col: 2,
code: "",
source: "t",
msg: "hm",
},
);
assert_eq!(mirror.apply_diag(&msg_lsp_diag(1, 2, 0, &records)), Some(2));
assert!(!mirror.files.contains_key("a.rs"));
assert_eq!(mirror.files["b.rs"].hash, hash(3));
assert_eq!(
mirror.apply_diag(&msg_lsp_diag(1, 3, LSP_DIAG_FULL, &[])),
Some(3)
);
assert!(mirror.files.is_empty());
}
#[test]
fn wire_fixtures() {
assert_eq!(
hex(&msg_lsp_open(
0x0102,
LSP_OPEN_WATCH | LSP_OPEN_DIAGS,
500,
"/src"
)),
"60020103f40104002f737263"
);
assert_eq!(hex(&msg_lsp_close(7)), "610700");
assert_eq!(
hex(&msg_lsp_ack(7, LSP_STREAM_DIAG, 0x01020304)),
"6207000104030201"
);
assert_eq!(
hex(&msg_lsp_query(&LspQueryRequest {
nonce: 3,
lsp_id: 7,
kind: LSP_QUERY_DEFINITION,
flags: 0,
line: 10,
col: 4,
path: "a.rs",
arg: "",
})),
"630300070001000a000000040000000400612e72730000"
);
assert_eq!(hex(&msg_lsp_cancel(10)), "640a00");
assert_eq!(hex(&msg_lsp_servers(11)), "650b00");
assert_eq!(hex(&msg_lsp_stop(12, 2)), "660c000200");
assert_eq!(
hex(&msg_lsp_opened(5, 1, LSP_STATUS_OK, 0, "/w", "")),
"6005000100000002002f770000"
);
assert_eq!(hex(&msg_lsp_closed(1, LSP_CLOSED_ROOT_GONE)), "64010001");
assert_eq!(hex(&msg_lsp_stopped(6, LSP_STATUS_OK)), "66060000");
let mut buf = Vec::new();
append_lsp_diag_record(
&mut buf,
&LspDiagRecord::File {
hash: hash(0xAB),
n: 1,
path: "a.rs",
},
);
assert_eq!(
hex(&buf),
format!("1900000001{}01000400612e7273", "ab".repeat(16))
);
let mut buf = Vec::new();
append_lsp_query_record(
&mut buf,
&LspQueryRecord::Markup {
format: LSP_MARKUP_MARKDOWN,
text: "hi",
},
);
assert_eq!(hex(&buf), "080000000201020000006869");
let mut buf = Vec::new();
append_lsp_query_record(
&mut buf,
&LspQueryRecord::Completion {
item_kind: 2,
flags: 0,
line: 1,
col: 2,
end_line: 1,
end_col: 4,
label: "ab",
insert: "",
detail: "T",
},
);
assert_eq!(
hex(&buf),
"1c00000005020001000000020000000100000004000000020061620000010054"
);
let mut buf = Vec::new();
append_lsp_query_record(
&mut buf,
&LspQueryRecord::Signature {
flags: LSP_SIGNATURE_ACTIVE,
active_param: 0,
param_start: 2,
param_end: 6,
label: "f(a: T)",
doc: "d",
},
);
assert_eq!(
hex(&buf),
"16000000060100000200060007006628613a2054290100000064"
);
}
}