Skip to content

Commit

Permalink
add tags to turbo tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
arlyon committed Apr 26, 2024
1 parent ce8e28a commit 69602cb
Show file tree
Hide file tree
Showing 19 changed files with 6,620 additions and 200 deletions.
1 change: 1 addition & 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 crates/turbo-static/Cargo.toml
Expand Up @@ -19,6 +19,7 @@ syn = { version = "2", features = ["parsing", "full", "visit", "extra-traits"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing.workspace = true
walkdir = "2.5.0"
serde = { workspace = true, features = ["derive"] }

[lints]
workspace = true
Binary file not shown.
Empty file.
Binary file not shown.
Binary file not shown.
6,091 changes: 6,091 additions & 0 deletions crates/turbo-static/graph.cypherl

Large diffs are not rendered by default.

Binary file removed crates/turbo-static/here
Binary file not shown.
125 changes: 125 additions & 0 deletions crates/turbo-static/src/call_resolver.rs
@@ -0,0 +1,125 @@
use fjall::PartitionCreateOptions;

use crate::{lsp_client::RAClient, Identifier, IdentifierReference};

pub struct CallResolver<'a> {
client: &'a mut RAClient,
fjall: &'a fjall::Keyspace,
handle: fjall::PartitionHandle,
}

impl<'a> CallResolver<'a> {
pub fn new(client: &'a mut RAClient, fjall: &'a fjall::Keyspace) -> Self {
let handle = fjall
.open_partition("links", PartitionCreateOptions::default())
.unwrap();
Self {
client,
fjall,
handle,
}
}

pub fn cached(&self) -> usize {
self.handle.len().unwrap()
}

pub fn cleared(mut self) -> Self {
self.fjall.delete_partition(self.handle).unwrap();
self.handle = self
.fjall
.open_partition("links", PartitionCreateOptions::default())
.unwrap();
self
}

pub fn resolve(&mut self, ident: &Identifier) -> Vec<IdentifierReference> {
if let Some(data) = self.handle.get(ident.to_string()).unwrap() {
tracing::info!("skipping {}", ident);
return bincode::deserialize(&data).unwrap();
};

tracing::info!("checking {}", ident);

let mut count = 0;
let _response = loop {
let response = self.client.request(lsp_server::Request {
id: 1.into(),
method: "textDocument/prepareCallHierarchy".to_string(),
params: serde_json::to_value(&lsp_types::CallHierarchyPrepareParams {
text_document_position_params: lsp_types::TextDocumentPositionParams {
position: ident.range.start,
text_document: lsp_types::TextDocumentIdentifier {
uri: lsp_types::Url::from_file_path(&ident.path).unwrap(),
},
},
work_done_progress_params: lsp_types::WorkDoneProgressParams {
work_done_token: Some(lsp_types::ProgressToken::String(
"prepare".to_string(),
)),
},
})
.unwrap(),
});
if let Some(Some(value)) = response.result.as_ref().map(|r| r.as_array()) {
if !value.is_empty() {
break value.to_owned();
}
count += 1;
}

// textDocument/prepareCallHierarchy will sometimes return an empty array so try
// at most 5 times
if count > 5 {
tracing::warn!("discovered isolated task {}", ident);
break vec![];
}

std::thread::sleep(std::time::Duration::from_secs(1));
};

// callHierarchy/incomingCalls
let response = self.client.request(lsp_server::Request {
id: 1.into(),
method: "callHierarchy/incomingCalls".to_string(),
params: serde_json::to_value(lsp_types::CallHierarchyIncomingCallsParams {
partial_result_params: lsp_types::PartialResultParams::default(),
item: lsp_types::CallHierarchyItem {
name: ident.name.to_owned(),
kind: lsp_types::SymbolKind::FUNCTION,
data: None,
tags: None,
detail: None,
uri: lsp_types::Url::from_file_path(&ident.path).unwrap(),
range: ident.range,
selection_range: ident.range,
},
work_done_progress_params: lsp_types::WorkDoneProgressParams {
work_done_token: Some(lsp_types::ProgressToken::String("prepare".to_string())),
},
})
.unwrap(),
});

let links = if let Some(e) = response.error {
tracing::warn!("unable to resolve {}: {:?}", ident, e);
vec![]
} else {
let response: Result<Vec<lsp_types::CallHierarchyIncomingCall>, _> =
serde_path_to_error::deserialize(response.result.unwrap());

response
.unwrap()
.into_iter()
.map(|i| i.into())
.collect::<Vec<IdentifierReference>>()
};

let data = bincode::serialize(&links).unwrap();

tracing::debug!("links: {:?}", links);

self.handle.insert(ident.to_string(), data).unwrap();
links
}
}
98 changes: 98 additions & 0 deletions crates/turbo-static/src/identifier.rs
@@ -0,0 +1,98 @@
use std::{fs, path::PathBuf};

use lsp_types::{CallHierarchyIncomingCall, CallHierarchyItem, Range};

/// A task that references another, with the range of the reference
#[derive(Hash, PartialEq, Eq, serde::Deserialize, serde::Serialize, Clone, Debug)]
pub struct IdentifierReference {
pub identifier: Identifier,
pub references: Vec<Range>, // the places where this identifier is used
}

/// identifies a task by its file, and range in the file
#[derive(Hash, PartialEq, Eq, serde::Deserialize, serde::Serialize, Clone)]
pub struct Identifier {
pub path: String,
// technically you can derive this from the name and range but it's easier to just store it
pub name: String,
// post_transform_name: Option<String>,
pub range: lsp_types::Range,
}

impl Identifier {
/// check the span matches and the text matches
///
/// `same_location` is used to check if the location of the identifier is
/// the same as the other
pub fn equals_ident(&self, other: &syn::Ident, match_location: bool) -> bool {
*other == self.name
&& (!match_location
|| (self.range.start.line == other.span().start().line as u32
&& self.range.start.character == other.span().start().column as u32))
}

fn get_name(item: &CallHierarchyItem) -> String {
// open file, find range inside, extract text
let file = fs::read_to_string(item.uri.path()).unwrap();

Check warning on line 36 in crates/turbo-static/src/identifier.rs

View workflow job for this annotation

GitHub Actions / Rust lints

Diff in /root/actions-runner/_work/turbo/turbo/crates/turbo-static/src/identifier.rs
let start = item.selection_range.start;
let end = item.selection_range.end;
file.lines().nth(start.line as usize)
.unwrap()
.chars()
.skip(start.character as usize)
.take(end.character as usize - start.character as usize)
.collect()
}
}

impl From<(PathBuf, syn::Ident)> for Identifier {
fn from((path, ident): (PathBuf, syn::Ident)) -> Self {
Self {
path: path.display().to_string(),
name: ident.to_string(),
// post_transform_name: None,
range: Range {
start: lsp_types::Position {
line: ident.span().start().line as u32 - 1,
character: ident.span().start().column as u32,
},
end: lsp_types::Position {
line: ident.span().end().line as u32 - 1,
character: ident.span().end().column as u32,
},
},
}
}
}

impl From<CallHierarchyIncomingCall> for IdentifierReference {
fn from(item: CallHierarchyIncomingCall) -> Self {
Self {
identifier: Identifier {
name: Identifier::get_name(&item.from),
// post_transform_name: Some(item.from.name),
path: item.from.uri.path().to_owned(),
range: item.from.selection_range,
},
references: item.from_ranges,
}
}
}

impl std::fmt::Debug for Identifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}

impl std::fmt::Display for Identifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}#{}",
self.path,
self.range.start.line,
self.name.to_string(),
)
}
}
34 changes: 14 additions & 20 deletions crates/turbo-static/src/lsp_client.rs
@@ -1,7 +1,7 @@
use std::{path::PathBuf, process};

use crossbeam_channel::bounded;
use lsp_server::{IoThreads, Message};
use lsp_server::Message;

/// An LSP client for Rust Analyzer (RA) that launches it as a subprocess.
pub struct RAClient {
Expand All @@ -23,6 +23,7 @@ impl RAClient {
.stdout(stdout)
.stderr(stderr)
// .env("RA_LOG", "info")
.env("RUST_BACKTRACE", "1")
.spawn()
.expect("Failed to start RA LSP server");
Self {
Expand All @@ -33,18 +34,18 @@ impl RAClient {
}

pub fn start(&mut self, folders: &[PathBuf]) {
let mut stdout = self.handle.stdout.take().unwrap();
let stdout = self.handle.stdout.take().unwrap();
let mut stdin = self.handle.stdin.take().unwrap();

let (writer_sender, writer_receiver) = bounded::<Message>(0);
let writer = std::thread::spawn(move || {
_ = std::thread::spawn(move || {
writer_receiver
.into_iter()
.try_for_each(|it| it.write(&mut stdin))
});

let (reader_sender, reader_receiver) = bounded::<Message>(0);
let reader = std::thread::spawn(move || {
_ = std::thread::spawn(move || {
let mut reader = std::io::BufReader::new(stdout);
while let Ok(Some(msg)) = Message::read(&mut reader) {
reader_sender
Expand All @@ -56,10 +57,6 @@ impl RAClient {
self.sender = Some(writer_sender);
self.receiver = Some(reader_receiver);

let root_path = std::fs::canonicalize(folders.first().unwrap())
.unwrap()
.to_string_lossy()
.to_string();
let workspace_paths = folders
.iter()
.map(|p| std::fs::canonicalize(p).unwrap())
Expand All @@ -69,12 +66,10 @@ impl RAClient {
})
.collect::<Vec<_>>();

let resp = self.request(lsp_server::Request {
_ = self.request(lsp_server::Request {
id: 1.into(),
method: "initialize".to_string(),
params: serde_json::to_value(&lsp_types::InitializeParams {
root_uri: Some(lsp_types::Url::from_file_path(&root_path).unwrap()),
root_path: Some(root_path),
params: serde_json::to_value(lsp_types::InitializeParams {
workspace_folders: Some(workspace_paths),
process_id: Some(std::process::id()),
capabilities: lsp_types::ClientCapabilities {
Expand All @@ -84,20 +79,19 @@ impl RAClient {
}),
..Default::default()
},
initialization_options: None,
work_done_progress_params: lsp_types::WorkDoneProgressParams {
work_done_token: Some(lsp_types::ProgressToken::String("prepare".to_string())),
},
trace: None,
client_info: None,
locale: None,
// we use workspace_folders so root_path and root_uri can be
// empty
..Default::default()
})
.unwrap(),
});

let resp = self.notify(lsp_server::Notification {
self.notify(lsp_server::Notification {
method: "initialized".to_string(),
params: serde_json::to_value(&lsp_types::InitializedParams {}).unwrap(),
params: serde_json::to_value(lsp_types::InitializedParams {}).unwrap(),
});
}

Expand Down Expand Up @@ -141,14 +135,14 @@ impl Drop for RAClient {
let resp = self.request(lsp_server::Request {
id: 1.into(),
method: "shutdown".to_string(),
params: serde_json::to_value(&()).unwrap(),
params: serde_json::to_value(()).unwrap(),
});

if resp.error.is_none() {
tracing::info!("shutting down RA LSP server");
self.notify(lsp_server::Notification {
method: "exit".to_string(),
params: serde_json::to_value(&()).unwrap(),
params: serde_json::to_value(()).unwrap(),
});
self.handle
.wait()
Expand Down

0 comments on commit 69602cb

Please sign in to comment.