Skip to content

Commit

Permalink
Bare-bones VSCode Extension
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Roberts authored Dec 8, 2022
1 parent fa4b106 commit 67a398c
Show file tree
Hide file tree
Showing 25 changed files with 3,362 additions and 226 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ node_modules
**/typings

# General
*.vsix
p4-analyzer.log
105 changes: 103 additions & 2 deletions crates/analyzer-host/src/fsm.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::json_rpc::{from_json, message::*, DeserializeError, ErrorCode};
use analyzer_abstractions::lsp_types::{
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
InitializeResult, ServerCapabilities, ServerInfo,
CompletionOptions, DeclarationCapability, DidChangeTextDocumentParams,
DidCloseTextDocumentParams, DidOpenTextDocumentParams, HoverProviderCapability,
ImplementationProviderCapability, InitializeResult, OneOf, ServerCapabilities, ServerInfo,
SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TraceValue,
TypeDefinitionProviderCapability, WorkDoneProgressOptions, CompletionParams, CompletionResponse, CompletionList, CompletionItem, HoverParams, Hover, HoverContents, MarkupContent, MarkupKind, CompletionItemKind,
};
use analyzer_abstractions::tracing::{error, info};
use analyzer_abstractions::{lsp_types::InitializeParams, LoggerImpl};
Expand Down Expand Up @@ -160,9 +163,44 @@ impl<'machine> ProtocolMachine<'machine> {
self.transition_to(ProtocolState::Initializing);

let params = from_json::<InitializeParams>("InitializeParams", &request.params)?;
let trace = params.trace.unwrap_or(TraceValue::Off);

info!("Tracevalue = {:?}", trace);

let result = InitializeResult {
capabilities: ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::INCREMENTAL,
)),
completion_provider: Some(CompletionOptions {
resolve_provider: Some(true),
trigger_characters: Some(vec![
"(".to_string(),
"<".to_string(),
".".to_string(),
]),
all_commit_characters: None,
work_done_progress_options: WorkDoneProgressOptions {
work_done_progress: None,
},
}),
hover_provider: Some(HoverProviderCapability::Simple(true)),
signature_help_provider: Some(SignatureHelpOptions {
trigger_characters: Some(vec!["(".to_string(), ",".to_string()]),
retrigger_characters: None,
work_done_progress_options: WorkDoneProgressOptions {
work_done_progress: None,
},
}),
declaration_provider: Some(DeclarationCapability::Simple(true)),
definition_provider: Some(OneOf::Left(true)),
type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(
true,
)),
implementation_provider: Some(ImplementationProviderCapability::Simple(
true,
)),
references_provider: Some(OneOf::Left(true)),
..Default::default()
},
server_info: Some(ServerInfo {
Expand Down Expand Up @@ -276,6 +314,69 @@ impl<'machine> ProtocolMachine<'machine> {
Ok(None)
}

Message::Request(request) if request.is("textDocument/hover") => {
info!("Received 'textDocument/completion' request.");

let params = from_json::<HoverParams>("HoverParams", &request.params)?;

let line = params.text_document_position_params.position.line;
let character = params.text_document_position_params.position.character;

let hover = Hover {
range: None,
contents: HoverContents::Markup(MarkupContent{
kind: MarkupKind::Markdown,
value: format!("Hovering over Ln *{}*, Col *{}*.", line, character)
})
};

Ok(Some(Message::Response(Response::new(
request.id,
hover,
))))
}

Message::Request(request) if request.is("textDocument/completion") => {
info!("Received 'textDocument/completion' request.");

let params = from_json::<CompletionParams>("dsd", &request.params)?;

let line = params.text_document_position.position.line;

let data = CompletionList {
is_incomplete: false,
items: vec![
CompletionItem {
label: String::from("p4"),
kind: Some(CompletionItemKind::FILE),
..Default::default()
},
CompletionItem {
label: String::from("rules"),
kind: Some(CompletionItemKind::FILE),
..Default::default()
}
]
};

Ok(Some(Message::Response(Response::new(
request.id,
data,
))))
}

Message::Notification(notification) => {
info!("Received '{}' notification.", notification.method);

Ok(None)
}

Message::Request(request) => {
info!("Received '{}' request.", request.method);

Ok(None)
}

_ => Err(ProtocolError::UnexpectedRequest),
}
}
Expand Down
6 changes: 5 additions & 1 deletion crates/analyzer-host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ impl<'host> AnalyzerHost<'host> {
.instrument(request_message_span)
.await;
}
Err(_) => continue,
Err(err) => {
error!("Unexpected error receving request: {:?}", err);

continue
},
}
}

Expand Down
20 changes: 14 additions & 6 deletions crates/analyzer-host/src/tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,27 @@ where
_ctx: tracing_subscriber::layer::Context<S>,
) {
// Ignore any events that do not carry a `lsp_event` field.
if !event.fields().any(|field| field.name() == "lsp_event") {
return;
}
// if !event.fields().any(|field| field.name() == "lsp_event") {
// return;
// }

let mut visitor = LspTracingMessageVisitor::new();

event.record(&mut visitor);

// let notification = Notification::new(
// "$/logTrace",
// LogTraceParams {
// message: visitor.message,
// verbose: None,
// },
// );

let notification = Notification::new(
"$/logTrace",
LogTraceParams {
"window/logMessage",
LogMessageParams {
message: visitor.message,
verbose: None,
typ: MessageType::LOG,
},
);

Expand Down
22 changes: 8 additions & 14 deletions crates/p4-analyzer-wasm/src/buffer.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
use js_sys::{ArrayBuffer, Uint8Array};
use js_sys::Uint8Array;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
/// A binding to the JavaScript built in [`Buffer`] class type that permits dealing with arbitary binary data
pub type Buffer;

#[wasm_bindgen(method, getter)]
pub(crate) fn buffer(this: &Buffer) -> ArrayBuffer;

#[wasm_bindgen(method, getter, js_name = byteOffset)]
fn byte_offset(this: &Buffer) -> u32;

#[wasm_bindgen(method, getter)]
fn length(this: &Buffer) -> u32;

/// Creates a [`Buffer`] from a [`Uint8Array`] without copying the underlying memory. The given array and the
/// created buffer will be mapped to the same memory.
#[wasm_bindgen(static_method_of = Buffer)]
fn from(array: Uint8Array) -> Buffer;
pub(crate) fn from(array: Uint8Array) -> Buffer;
}

/// Converts an ArrayBuffer to a new vector of `u8`.
pub(crate) fn to_u8_vec(buffer: &ArrayBuffer) -> Vec<u8> {
/// Converts a [`Buffer`] into a new vector of `u8`.
pub(crate) fn to_u8_vec(buffer: &Buffer) -> Vec<u8> {
Uint8Array::new(buffer).to_vec()
}

/// Converts a `u8` slice into a new Buffer.
/// Converts a `u8` slice into a new [`Buffer`].
pub(crate) fn to_buffer(src: &[u8]) -> Buffer {
let array_buffer = Uint8Array::new_with_length(src.len() as u32);

Expand Down
59 changes: 38 additions & 21 deletions crates/p4-analyzer-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ use buffer::{to_buffer, to_u8_vec, Buffer};
use cancellation::{CancellationTokenSource, OperationCanceled};
use futures::future::join;
use js_sys::Error;
use std::panic;
use serde::Serialize;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct LspServer {
on_receive_cb: js_sys::Function,
on_response_callback: js_sys::Function,
request_channel: MessageChannel,
response_channel: MessageChannel,
cts: CancellationTokenSource,
Expand All @@ -30,11 +30,11 @@ pub struct LspServer {
impl LspServer {
/// Initializes a new [`LspServer`] instance.
#[wasm_bindgen(constructor)]
pub fn new(on_receive_cb: js_sys::Function) -> Self {
pub fn new(on_response_callback: js_sys::Function) -> Self {
console_error_panic_hook::set_once();

Self {
on_receive_cb,
on_response_callback,
request_channel: async_channel::unbounded::<Message>(),
response_channel: async_channel::unbounded::<Message>(),
cts: CancellationTokenSource::new(),
Expand All @@ -55,23 +55,26 @@ impl LspServer {
}
}

/// Updates the underlying input buffer and attempts to read a [`Message`] from it. If a [`Message`] could be read
/// from the input buffer then it is sent to the underlying request channel for processing by the [`AnalyzerHost`].
#[wasm_bindgen(js_name = "sendRequestBuffer")]
pub async fn send_request_buffer(&self, request_buffer: Buffer) -> Result<Buffer, JsValue> {
let mut message_buffer = &to_u8_vec(&request_buffer.buffer())[..];

match Message::read(&mut message_buffer) {
Ok(Some(message)) => {
/// Reads a request message from a given `Buffer`, and sends it to the underlying request channel for processing
/// by the [`AnalyzerHost`].
#[wasm_bindgen(js_name = "sendRequest")]
pub async fn send_request(&self, request_buffer: Buffer) -> Result<JsValue, JsValue> {
match serde_json::from_slice::<Message>(to_u8_vec(&request_buffer).as_slice()) {
Ok(message) => {
let (sender, _) = self.request_channel.clone();

if sender.send(message).await.is_err() {
return Err(JsValue::from(Error::new("Unexpected error writing request message to request channel.")));
return Err(JsValue::from(Error::new(
"Unexpected error writing request message to request channel.",
)));
}

Ok(to_buffer(message_buffer)) // Return a buffer over the modified `message_buffer`.
Ok(JsValue::UNDEFINED)
}
_ => Ok(request_buffer), // Return the unmodified `request_buffer`.
Err(err) => Err(JsValue::from(Error::new(&format!(
"Unexpected error reading request message: {}",
err.to_string()
)))),
}
}

Expand All @@ -85,17 +88,28 @@ impl LspServer {
receiver.close();
}

/// Asynchronously receives response messages from the response channel, converts them to a Buffer instance, and
/// Asynchronously receives response messages from the response channel, converts them to a `Buffer`, and
/// then invokes the receiver callback provided by the JavaScript host.
async fn on_receive(&self) -> Result<(), OperationCanceled> {
#[derive(Serialize)]
struct JsonRpcEnvelope {
jsonrpc: &'static str,

#[serde(flatten)]
msg: Message,
}

let (_, receiver) = self.response_channel.clone();

while let Ok(message) = receiver.recv().await {
let mut buffer = Vec::<u8>::new();

message.write(&mut buffer).unwrap();
self.on_receive_cb
.call1(&JsValue::NULL, &to_buffer(&buffer))
let message_text = serde_json::to_string(&JsonRpcEnvelope {
jsonrpc: "2.0",
msg: message,
})
.unwrap();

self.on_response_callback
.call1(&JsValue::NULL, &to_buffer(message_text.as_bytes()))
.unwrap();
}

Expand All @@ -114,6 +128,9 @@ impl LspServer {
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);

#[wasm_bindgen(js_namespace = console)]
fn error(s: &str);
}

struct ConsoleLogger {}
Expand Down
6 changes: 6 additions & 0 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
}
},
"targetDependencies": {
"package": [
{
"target": "build",
"projects": "self"
}
],
"build": [
{
"target": "build",
Expand Down
Loading

0 comments on commit 67a398c

Please sign in to comment.