Skip to content

Commit

Permalink
feat: client capabilities && refactor debouncer
Browse files Browse the repository at this point in the history
  • Loading branch information
washanhanzi committed Nov 14, 2024
1 parent ee2f812 commit a5b109d
Show file tree
Hide file tree
Showing 12 changed files with 18,713 additions and 107 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ cargo-lock = { git = "https://github.com/washanhanzi/rustsec", branch = "main",
"dependency-tree",
] }
petgraph = "0.6.5"
tokio-util = { version = "0.7.12", features = ["time"] }

[features]
default = []
Expand Down
18,572 changes: 18,540 additions & 32 deletions editor/code/dist/extension.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion editor/code/dist/extension.js.map

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion editor/code/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export async function activate(context: ExtensionContext) {
},
debug: {
command: serverPath,
args: ["--renderer", "vscode"],
args: ["--renderer", "vscode", "--client-capabilities", "readFile"],
transport: TransportKind.stdio,
}
}
Expand Down Expand Up @@ -127,13 +127,15 @@ async function languageServerBinaryPath(context: ExtensionContext): Promise<stri

//show status bar message with version info
const statusBarItem = window.createStatusBarItem("download-lsp-server", StatusBarAlignment.Left, 0)

//claude write this
// let spinnerIndex = 0
// const spinnerChars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
// const spinnerInterval = setInterval(() => {
// statusBarItem.text = `${spinnerChars[spinnerIndex]} Downloading Cargo-appraiser LSP server ${releaseInfo.tag_name}...`
// spinnerIndex = (spinnerIndex + 1) % spinnerChars.length
// }, 100)

statusBarItem.text = `$(loading~spin) Downloading cargo-appraiser server ${releaseInfo.tag_name}...`
statusBarItem.show()

Expand Down
2 changes: 2 additions & 0 deletions src/controller.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod appraiser;
mod audit;
mod capabilities;
mod cargo;
mod code_action;
mod completion;
Expand All @@ -9,3 +10,4 @@ mod hover;
mod read_file;

pub use appraiser::{Appraiser, CargoDocumentEvent, CargoTomlPayload};
pub use capabilities::ClientCapability;
66 changes: 50 additions & 16 deletions src/controller/appraiser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{

use super::{
audit::{into_diagnostic_severity, AuditController, AuditReports, AuditResult},
capabilities::{ClientCapabilities, ClientCapability},
cargo::{cargo_resolve, CargoResolveOutput},
debouncer::Debouncer,
diagnostic::DiagnosticController,
Expand All @@ -44,6 +45,7 @@ pub struct Ctx {
pub struct Appraiser {
client: Client,
render_tx: Sender<DecorationEvent>,
client_capabilities: ClientCapabilities,
}

pub enum CargoDocumentEvent {
Expand Down Expand Up @@ -76,8 +78,17 @@ pub struct CargoTomlPayload {
}

impl Appraiser {
pub fn new(client: Client, render_tx: Sender<DecorationEvent>) -> Self {
Self { client, render_tx }
pub fn new(
client: Client,
render_tx: Sender<DecorationEvent>,
client_capabilities: &[ClientCapability],
) -> Self {
let client_capabilities = ClientCapabilities::new(client_capabilities);
Self {
client,
render_tx,
client_capabilities,
}
}
pub fn initialize(&self) -> Sender<CargoDocumentEvent> {
//create mpsc channel
Expand Down Expand Up @@ -123,6 +134,7 @@ impl Appraiser {
//render task sender
let render_tx = self.render_tx.clone();
let client = self.client.clone();
let client_capabilities = self.client_capabilities.clone();
tokio::spawn(async move {
//workspace state
let mut state = Workspace::new();
Expand Down Expand Up @@ -379,21 +391,38 @@ impl Appraiser {

if let Some(uri) = doc.root_manifest.as_ref() {
if uri != &msg.uri {
let param = ReadFileParam { uri: uri.clone() };
match client.send_request::<ReadFile>(param).await {
Ok(content) => {
if let Err(e) = inner_tx
.send(CargoDocumentEvent::Opened(CargoTomlPayload {
uri: uri.clone(),
text: content.content,
}))
.await
{
error!("send root manifest error: {}", e);
if client_capabilities.can_read_file() {
let param = ReadFileParam { uri: uri.clone() };
match client.send_request::<ReadFile>(param).await {
Ok(content) => {
if let Err(e) = inner_tx
.send(CargoDocumentEvent::Opened(
CargoTomlPayload {
uri: uri.clone(),
text: content.content,
},
))
.await
{
error!("inner tx send error: {}", e);
}
}
Err(e) => {
error!("read file error: {}", e);
}
}
Err(e) => {
error!("read file error: {}", e);
} else {
//read file with os
let content =
std::fs::read_to_string(uri.path().as_str()).unwrap();
if let Err(e) = inner_tx
.send(CargoDocumentEvent::Opened(CargoTomlPayload {
uri: uri.clone(),
text: content,
}))
.await
{
error!("inner tx send error: {}", e);
}
}
}
Expand Down Expand Up @@ -550,7 +579,12 @@ async fn start_resolve(
};
doc.populate_dependencies();

//no change to resolve
//virtual workspace doesn't need to resolve
if doc.is_virtual() {
return;
}

//no change to document
if !doc.is_dirty() {
return;
}
Expand Down
25 changes: 25 additions & 0 deletions src/controller/capabilities.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#[derive(clap::ValueEnum, Debug, Clone, PartialEq, Eq, Hash)]
pub enum ClientCapability {
#[value(name = "readFile")]
ReadFile,
}

#[derive(Debug, Clone)]
pub struct ClientCapabilities {
read_file: bool,
}

impl ClientCapabilities {
pub fn new(client_capabilities: &[ClientCapability]) -> Self {
let mut c = ClientCapabilities { read_file: false };
for capability in client_capabilities {
match capability {
ClientCapability::ReadFile => c.read_file = true,
}
}
c
}
pub fn can_read_file(&self) -> bool {
self.read_file
}
}
3 changes: 2 additions & 1 deletion src/controller/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub struct CargoResolveOutput {

#[tracing::instrument(name = "cargo_resolve", level = "trace")]
pub async fn cargo_resolve(ctx: &Ctx) -> Result<CargoResolveOutput, CargoError> {
info!("start resolve {}", ctx.rev);
let path = Path::new(ctx.uri.path().as_str());
let gctx = cargo::util::context::GlobalContext::default().map_err(CargoError::resolve_error)?;
let workspace =
Expand Down Expand Up @@ -113,7 +114,7 @@ pub async fn cargo_resolve(ctx: &Ctx) -> Result<CargoResolveOutput, CargoError>
);
}
}

info!("finished resolve inside {}", ctx.rev);
Ok(CargoResolveOutput {
ctx: ctx.clone(),
dependencies: res,
Expand Down
132 changes: 78 additions & 54 deletions src/controller/debouncer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use super::{appraiser::Ctx, CargoDocumentEvent};
use std::{pin::Pin, time::Duration};
use futures::{Stream, StreamExt};
use std::{
collections::HashMap,
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use tokio::sync::mpsc::{self, error::SendError, Sender};
use tokio::time::{sleep, Sleep};
use tokio_util::time::{delay_queue, DelayQueue};
use tower_lsp::lsp_types::Uri;
use tracing::error;

Expand All @@ -18,6 +24,64 @@ pub enum DebouncerEvent {
Background(Ctx),
}

pub struct Queue {
entries: HashMap<Uri, (usize, delay_queue::Key)>,
expirations: DelayQueue<Uri>,
backoff_factor: HashMap<Uri, u32>,
background_timeout: u64,
interactive_timeout: u64,
}

impl Queue {
pub fn new(interactive_timeout: u64, background_timeout: u64) -> Self {
Self {
entries: HashMap::new(),
expirations: DelayQueue::new(),
backoff_factor: HashMap::new(),
background_timeout,
interactive_timeout,
}
}

pub fn insert_interactive(&mut self, ctx: Ctx) {
self.backoff_factor.remove(&ctx.uri);
let key = self.expirations.insert(
ctx.uri.clone(),
Duration::from_millis(self.interactive_timeout),
);
self.entries.insert(ctx.uri, (ctx.rev, key));
}

pub fn insert_background(&mut self, ctx: Ctx) {
let factor = self.backoff_factor.entry(ctx.uri.clone()).or_insert(0);
*factor += 1;
let timeout = calculate_backoff_timeout(self.background_timeout, *factor);
let key = self
.expirations
.insert(ctx.uri.clone(), Duration::from_millis(timeout));
self.entries.insert(ctx.uri, (ctx.rev, key));
}
}

impl Stream for Queue {
type Item = Ctx;

fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
match this.expirations.poll_expired(cx) {
Poll::Ready(Some(expired)) => match this.entries.remove(&expired.get_ref().clone()) {
Some((rev, _)) => Poll::Ready(Some(Ctx {
uri: expired.get_ref().clone(),
rev,
})),
None => Poll::Ready(None),
},
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
}
}

impl Debouncer {
pub fn new(
tx: Sender<CargoDocumentEvent>,
Expand Down Expand Up @@ -53,66 +117,26 @@ impl Debouncer {
let (internal_tx, mut internal_rx) = mpsc::channel::<DebouncerEvent>(64);
self.sender = Some(internal_tx);
let tx = self.tx.clone();
let interactive_timeout = self.interactive_timeout;
let background_timeout = self.background_timeout;
let mut q = Queue::new(self.interactive_timeout, self.background_timeout);

tokio::spawn(async move {
let mut uri: Option<Uri> = None;
let mut rev = 0;
let mut delay: Option<Pin<Box<Sleep>>> = None;
let mut backoff_uri: Option<Uri> = None;
let mut backoff_factor: u32 = 0;

loop {
tokio::select! {
// Handle incoming Ctx messages
some_event = internal_rx.recv() => {
let Some(event) = some_event else{break};
let timeout=match event {
DebouncerEvent::Interactive(ctx) => {
uri = Some(ctx.uri.clone());
rev = ctx.rev;
//reset backoff
backoff_uri = Some(ctx.uri.clone());
backoff_factor = 0;
interactive_timeout
},
DebouncerEvent::Background(ctx) => {
if let Some(uri) = &backoff_uri {
if uri == &ctx.uri {
backoff_factor += 1;
} else {
backoff_uri = Some(ctx.uri.clone());
backoff_factor = 0;
}
}
uri = Some(ctx.uri.clone());
rev = ctx.rev;
calculate_backoff_timeout(background_timeout, backoff_factor)
}
Some(event) = internal_rx.recv() => {
match event {
DebouncerEvent::Interactive(ctx) => {
q.insert_interactive(ctx);
},
DebouncerEvent::Background(ctx) => {
q.insert_background(ctx);
}
};
delay = Some(Box::pin(sleep(Duration::from_millis(timeout))));
}

// Handle the delay if it's set
() = async {
if let Some(ref mut d) = delay {
d.await
} else {
futures::future::pending::<()>().await
}
}, if delay.is_some() => {
if let Some(current_uri) = &uri {
let ctx = Ctx {
uri: current_uri.clone(),
rev,
};
if let Err(e) = tx.send(CargoDocumentEvent::ReadyToResolve(ctx)).await {
error!("failed to send Ctx from debouncer: {}", e);
}
Some(ctx) = q.next() => {
if let Err(e) = tx.send(CargoDocumentEvent::ReadyToResolve(ctx)).await {
error!("failed to send Ctx from debouncer: {}", e);
}
// Reset the delay
delay = None;
}
}
}
Expand Down
Loading

0 comments on commit a5b109d

Please sign in to comment.