Merge commit '258b15c506' into sync-from-ra

This commit is contained in:
Laurențiu Nicola 2023-09-18 12:32:37 +03:00
parent 7e786ea4cf
commit bcfc997eac
195 changed files with 5773 additions and 2750 deletions

View file

@ -50,7 +50,6 @@ always-assert = "0.1.2"
# These 3 deps are not used by r-a directly, but we list them here to lock in their versions
# in our transitive deps to prevent them from pulling in windows-sys 0.45.0
mio = "=0.8.5"
filetime = "=0.2.19"
parking_lot_core = "=0.9.6"
cfg.workspace = true

View file

@ -16,10 +16,12 @@ use lsp_types::{
};
use serde_json::json;
use crate::config::{Config, RustfmtConfig};
use crate::line_index::PositionEncoding;
use crate::lsp_ext::negotiated_encoding;
use crate::semantic_tokens;
use crate::{
config::{Config, RustfmtConfig},
line_index::PositionEncoding,
lsp::semantic_tokens,
lsp_ext::negotiated_encoding,
};
pub fn server_capabilities(config: &Config) -> ServerCapabilities {
ServerCapabilities {
@ -218,7 +220,7 @@ fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProvi
}
fn more_trigger_character(config: &Config) -> Vec<String> {
let mut res = vec![".".to_string(), ">".to_string(), "{".to_string()];
let mut res = vec![".".to_string(), ">".to_string(), "{".to_string(), "(".to_string()];
if config.snippet_cap() {
res.push("<".to_string());
}

View file

@ -15,7 +15,10 @@ use hir_def::{
hir::{ExprId, PatId},
};
use hir_ty::{Interner, Substitution, TyExt, TypeFlags};
use ide::{Analysis, AnnotationConfig, DiagnosticsConfig, InlayHintsConfig, LineCol, RootDatabase};
use ide::{
Analysis, AnnotationConfig, DiagnosticsConfig, InlayFieldsToResolve, InlayHintsConfig, LineCol,
RootDatabase,
};
use ide_db::{
base_db::{
salsa::{self, debug::DebugQueryTable, ParallelDatabase},
@ -317,9 +320,13 @@ impl flags::AnalysisStats {
fn run_mir_lowering(&self, db: &RootDatabase, bodies: &[DefWithBody], verbosity: Verbosity) {
let mut sw = self.stop_watch();
let all = bodies.len() as u64;
let mut all = 0;
let mut fail = 0;
for &body in bodies {
if matches!(body, DefWithBody::Variant(_)) {
continue;
}
all += 1;
let Err(e) = db.mir_body(body.into()) else {
continue;
};
@ -782,6 +789,7 @@ impl flags::AnalysisStats {
closure_style: hir::ClosureStyle::ImplFn,
max_length: Some(25),
closing_brace_hints_min_lines: Some(20),
fields_to_resolve: InlayFieldsToResolve::empty(),
},
file_id,
None,

View file

@ -21,7 +21,7 @@ use vfs::{AbsPathBuf, Vfs};
use crate::{
cli::flags,
line_index::{LineEndings, LineIndex, PositionEncoding},
to_proto,
lsp::to_proto,
version::version,
};

View file

@ -51,7 +51,7 @@ impl flags::Scip {
version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(),
tool_info: Some(scip_types::ToolInfo {
name: "rust-analyzer".to_owned(),
version: "0.1".to_owned(),
version: format!("{}", crate::version::version()),
arguments: vec![],
special_fields: Default::default(),
})

View file

@ -13,8 +13,9 @@ use cfg::{CfgAtom, CfgDiff};
use flycheck::FlycheckConfig;
use ide::{
AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig,
JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, Snippet, SnippetScope,
HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve,
InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind,
Snippet, SnippetScope,
};
use ide_db::{
imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
@ -1335,6 +1336,18 @@ impl Config {
}
pub fn inlay_hints(&self) -> InlayHintsConfig {
let client_capability_fields = self
.caps
.text_document
.as_ref()
.and_then(|text| text.inlay_hint.as_ref())
.and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref())
.map(|inlay_resolve| inlay_resolve.properties.iter())
.into_iter()
.flatten()
.cloned()
.collect::<FxHashSet<_>>();
InlayHintsConfig {
render_colons: self.data.inlayHints_renderColons,
type_hints: self.data.inlayHints_typeHints_enable,
@ -1395,6 +1408,13 @@ impl Config {
} else {
None
},
fields_to_resolve: InlayFieldsToResolve {
resolve_text_edits: client_capability_fields.contains("textEdits"),
resolve_hint_tooltip: client_capability_fields.contains("tooltip"),
resolve_label_tooltip: client_capability_fields.contains("label.tooltip"),
resolve_label_location: client_capability_fields.contains("label.location"),
resolve_label_command: client_capability_fields.contains("label.command"),
},
}
}

View file

@ -9,7 +9,7 @@ use nohash_hasher::{IntMap, IntSet};
use rustc_hash::FxHashSet;
use triomphe::Arc;
use crate::lsp_ext;
use crate::{global_state::GlobalStateSnapshot, lsp, lsp_ext};
pub(crate) type CheckFixes = Arc<IntMap<usize, IntMap<FileId, Vec<Fix>>>>;
@ -122,3 +122,41 @@ fn are_diagnostics_equal(left: &lsp_types::Diagnostic, right: &lsp_types::Diagno
&& left.range == right.range
&& left.message == right.message
}
pub(crate) fn fetch_native_diagnostics(
snapshot: GlobalStateSnapshot,
subscriptions: Vec<FileId>,
) -> Vec<(FileId, Vec<lsp_types::Diagnostic>)> {
let _p = profile::span("fetch_native_diagnostics");
let _ctx = stdx::panic_context::enter("fetch_native_diagnostics".to_owned());
subscriptions
.into_iter()
.filter_map(|file_id| {
let line_index = snapshot.file_line_index(file_id).ok()?;
let diagnostics = snapshot
.analysis
.diagnostics(
&snapshot.config.diagnostics(),
ide::AssistResolveStrategy::None,
file_id,
)
.ok()?
.into_iter()
.map(move |d| lsp_types::Diagnostic {
range: lsp::to_proto::range(&line_index, d.range),
severity: Some(lsp::to_proto::diagnostic_severity(d.severity)),
code: Some(lsp_types::NumberOrString::String(d.code.as_str().to_string())),
code_description: Some(lsp_types::CodeDescription {
href: lsp_types::Url::parse(&d.code.url()).unwrap(),
}),
source: Some("rust-analyzer".to_string()),
message: d.message,
related_information: None,
tags: d.unused.then(|| vec![lsp_types::DiagnosticTag::UNNECESSARY]),
data: None,
})
.collect::<Vec<_>>();
Some((file_id, diagnostics))
})
.collect()
}

View file

@ -8,8 +8,8 @@ use stdx::format_to;
use vfs::{AbsPath, AbsPathBuf};
use crate::{
global_state::GlobalStateSnapshot, line_index::PositionEncoding, lsp_ext,
to_proto::url_from_abs_path,
global_state::GlobalStateSnapshot, line_index::PositionEncoding,
lsp::to_proto::url_from_abs_path, lsp_ext,
};
use super::{DiagnosticsMapConfig, Fix};

View file

@ -8,9 +8,9 @@ use stdx::thread::ThreadIntent;
use crate::{
global_state::{GlobalState, GlobalStateSnapshot},
lsp::LspError,
main_loop::Task,
version::version,
LspError,
};
/// A visitor for routing a raw JSON request to an appropriate handler function.

View file

@ -12,25 +12,27 @@ use ide_db::base_db::{CrateId, FileLoader, ProcMacroPaths, SourceDatabase};
use load_cargo::SourceRootConfig;
use lsp_types::{SemanticTokens, Url};
use nohash_hasher::IntMap;
use parking_lot::{Mutex, RwLock};
use parking_lot::{
MappedRwLockReadGuard, Mutex, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard,
RwLockWriteGuard,
};
use proc_macro_api::ProcMacroServer;
use project_model::{CargoWorkspace, ProjectWorkspace, Target, WorkspaceBuildScripts};
use rustc_hash::{FxHashMap, FxHashSet};
use triomphe::Arc;
use vfs::AnchoredPathBuf;
use vfs::{AnchoredPathBuf, Vfs};
use crate::{
config::{Config, ConfigError},
diagnostics::{CheckFixes, DiagnosticCollection},
from_proto,
line_index::{LineEndings, LineIndex},
lsp::{from_proto, to_proto::url_from_abs_path},
lsp_ext,
main_loop::Task,
mem_docs::MemDocs,
op_queue::OpQueue,
reload,
task_pool::TaskPool,
to_proto::url_from_abs_path,
};
// Enforces drop order
@ -40,7 +42,7 @@ pub(crate) struct Handle<H, C> {
}
pub(crate) type ReqHandler = fn(&mut GlobalState, lsp_server::Response);
pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
/// `GlobalState` is the primary mutable state of the language server
///
@ -49,6 +51,7 @@ pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
/// incremental salsa database.
///
/// Note that this struct has more than one impl in various modules!
#[doc(alias = "GlobalMess")]
pub(crate) struct GlobalState {
sender: Sender<lsp_server::Message>,
req_queue: ReqQueue,
@ -66,6 +69,7 @@ pub(crate) struct GlobalState {
// status
pub(crate) shutdown_requested: bool,
pub(crate) send_hint_refresh_query: bool,
pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>,
// proc macros
@ -177,6 +181,7 @@ impl GlobalState {
mem_docs: MemDocs::default(),
semantic_tokens_cache: Arc::new(Default::default()),
shutdown_requested: false,
send_hint_refresh_query: false,
last_reported_status: None,
source_root_config: SourceRootConfig::default(),
config_errors: Default::default(),
@ -216,12 +221,15 @@ impl GlobalState {
let mut file_changes = FxHashMap::default();
let (change, changed_files, workspace_structure_change) = {
let mut change = Change::new();
let (vfs, line_endings_map) = &mut *self.vfs.write();
let changed_files = vfs.take_changes();
let mut guard = self.vfs.write();
let changed_files = guard.0.take_changes();
if changed_files.is_empty() {
return false;
}
// downgrade to read lock to allow more readers while we are normalizing text
let guard = RwLockWriteGuard::downgrade_to_upgradable(guard);
let vfs: &Vfs = &guard.0;
// We need to fix up the changed events a bit. If we have a create or modify for a file
// id that is followed by a delete we actually skip observing the file text from the
// earlier event, to avoid problems later on.
@ -272,6 +280,7 @@ impl GlobalState {
let mut workspace_structure_change = None;
// A file was added or deleted
let mut has_structure_changes = false;
let mut bytes = vec![];
for file in &changed_files {
let vfs_path = &vfs.file_path(file.file_id);
if let Some(path) = vfs_path.as_path() {
@ -293,16 +302,28 @@ impl GlobalState {
let text = if file.exists() {
let bytes = vfs.file_contents(file.file_id).to_vec();
String::from_utf8(bytes).ok().and_then(|text| {
// FIXME: Consider doing normalization in the `vfs` instead? That allows
// getting rid of some locking
let (text, line_endings) = LineEndings::normalize(text);
line_endings_map.insert(file.file_id, line_endings);
Some(Arc::from(text))
Some((Arc::from(text), line_endings))
})
} else {
None
};
change.change_file(file.file_id, text);
// delay `line_endings_map` changes until we are done normalizing the text
// this allows delaying the re-acquisition of the write lock
bytes.push((file.file_id, text));
}
let (vfs, line_endings_map) = &mut *RwLockUpgradableReadGuard::upgrade(guard);
bytes.into_iter().for_each(|(file_id, text)| match text {
None => change.change_file(file_id, None),
Some((text, line_endings)) => {
line_endings_map.insert(file_id, line_endings);
change.change_file(file_id, Some(text));
}
});
if has_structure_changes {
let roots = self.source_root_config.partition(vfs);
change.set_roots(roots);
@ -422,12 +443,16 @@ impl Drop for GlobalState {
}
impl GlobalStateSnapshot {
fn vfs_read(&self) -> MappedRwLockReadGuard<'_, vfs::Vfs> {
RwLockReadGuard::map(self.vfs.read(), |(it, _)| it)
}
pub(crate) fn url_to_file_id(&self, url: &Url) -> anyhow::Result<FileId> {
url_to_file_id(&self.vfs.read().0, url)
url_to_file_id(&self.vfs_read(), url)
}
pub(crate) fn file_id_to_url(&self, id: FileId) -> Url {
file_id_to_url(&self.vfs.read().0, id)
file_id_to_url(&self.vfs_read(), id)
}
pub(crate) fn file_line_index(&self, file_id: FileId) -> Cancellable<LineIndex> {
@ -443,7 +468,7 @@ impl GlobalStateSnapshot {
}
pub(crate) fn anchored_path(&self, path: &AnchoredPathBuf) -> Url {
let mut base = self.vfs.read().0.file_path(path.anchor);
let mut base = self.vfs_read().file_path(path.anchor);
base.pop();
let path = base.join(&path.path).unwrap();
let path = path.as_path().unwrap();
@ -451,7 +476,7 @@ impl GlobalStateSnapshot {
}
pub(crate) fn file_id_to_file_path(&self, file_id: FileId) -> vfs::VfsPath {
self.vfs.read().0.file_path(file_id)
self.vfs_read().file_path(file_id)
}
pub(crate) fn cargo_target_for_crate_root(
@ -459,7 +484,7 @@ impl GlobalStateSnapshot {
crate_id: CrateId,
) -> Option<(&CargoWorkspace, Target)> {
let file_id = self.analysis.crate_root(crate_id).ok()?;
let path = self.vfs.read().0.file_path(file_id);
let path = self.vfs_read().file_path(file_id);
let path = path.as_path()?;
self.workspaces.iter().find_map(|ws| match ws {
ProjectWorkspace::Cargo { cargo, .. } => {
@ -471,7 +496,11 @@ impl GlobalStateSnapshot {
}
pub(crate) fn vfs_memory_usage(&self) -> usize {
self.vfs.read().0.memory_usage()
self.vfs_read().memory_usage()
}
pub(crate) fn file_exists(&self, file_id: FileId) -> bool {
self.vfs.read().0.exists(file_id)
}
}

View file

@ -13,8 +13,12 @@ use triomphe::Arc;
use vfs::{AbsPathBuf, ChangeKind, VfsPath};
use crate::{
config::Config, from_proto, global_state::GlobalState, lsp_ext::RunFlycheckParams,
lsp_utils::apply_document_changes, mem_docs::DocumentData, reload,
config::Config,
global_state::GlobalState,
lsp::{from_proto, utils::apply_document_changes},
lsp_ext::RunFlycheckParams,
mem_docs::DocumentData,
reload,
};
pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> anyhow::Result<()> {
@ -84,15 +88,16 @@ pub(crate) fn handle_did_change_text_document(
}
};
let vfs = &mut state.vfs.write().0;
let file_id = vfs.file_id(&path).unwrap();
let text = apply_document_changes(
state.config.position_encoding(),
|| std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into(),
|| {
let vfs = &state.vfs.read().0;
let file_id = vfs.file_id(&path).unwrap();
std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into()
},
params.content_changes,
);
vfs.set_file_contents(path, Some(text.into_bytes()));
state.vfs.write().0.set_file_contents(path, Some(text.into_bytes()));
}
Ok(())
}
@ -108,6 +113,10 @@ pub(crate) fn handle_did_close_text_document(
tracing::error!("orphan DidCloseTextDocument: {}", path);
}
if let Some(file_id) = state.vfs.read().0.file_id(&path) {
state.diagnostics.clear_native_for(file_id);
}
state.semantic_tokens_cache.lock().remove(&params.text_document.uri);
if let Some(path) = path.as_path() {

View file

@ -11,8 +11,8 @@ use anyhow::Context;
use ide::{
AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange,
HoverAction, HoverGotoTypeData, Query, RangeInfo, ReferenceCategory, Runnable, RunnableKind,
SingleResolve, SourceChange, TextEdit,
HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory,
Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
};
use ide_db::SymbolKind;
use lsp_server::ErrorCode;
@ -30,21 +30,23 @@ use serde_json::json;
use stdx::{format_to, never};
use syntax::{algo, ast, AstNode, TextRange, TextSize};
use triomphe::Arc;
use vfs::{AbsPath, AbsPathBuf, VfsPath};
use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
use crate::{
cargo_target_spec::CargoTargetSpec,
config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
diff::diff,
from_proto,
global_state::{GlobalState, GlobalStateSnapshot},
line_index::LineEndings,
lsp::{
from_proto, to_proto,
utils::{all_edits_are_disjoint, invalid_params_error},
LspError,
},
lsp_ext::{
self, CrateInfoResult, ExternalDocsPair, ExternalDocsResponse, FetchDependencyListParams,
FetchDependencyListResult, PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams,
},
lsp_utils::{all_edits_are_disjoint, invalid_params_error},
to_proto, LspError,
};
pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
@ -354,7 +356,7 @@ pub(crate) fn handle_on_type_formatting(
// This should be a single-file edit
let (_, (text_edit, snippet_edit)) = edit.source_file_edits.into_iter().next().unwrap();
stdx::never!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets");
stdx::always!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets");
let change = to_proto::snippet_text_edit_vec(&line_index, edit.is_snippet, text_edit);
Ok(Some(change))
@ -972,7 +974,7 @@ pub(crate) fn handle_hover(
PositionOrRange::Range(range) => range,
};
let file_range = from_proto::file_range(&snap, params.text_document, range)?;
let file_range = from_proto::file_range(&snap, &params.text_document, range)?;
let info = match snap.analysis.hover(&snap.config.hover(), file_range)? {
None => return Ok(None),
Some(info) => info,
@ -1128,7 +1130,7 @@ pub(crate) fn handle_code_action(
let line_index =
snap.file_line_index(from_proto::file_id(&snap, &params.text_document.uri)?)?;
let frange = from_proto::file_range(&snap, params.text_document.clone(), params.range)?;
let frange = from_proto::file_range(&snap, &params.text_document, params.range)?;
let mut assists_config = snap.config.assist();
assists_config.allowed = params
@ -1381,7 +1383,7 @@ pub(crate) fn handle_ssr(
let selections = params
.selections
.iter()
.map(|range| from_proto::file_range(&snap, params.position.text_document.clone(), *range))
.map(|range| from_proto::file_range(&snap, &params.position.text_document, *range))
.collect::<Result<Vec<_>, _>>()?;
let position = from_proto::file_position(&snap, params.position)?;
let source_change = snap.analysis.structural_search_replace(
@ -1401,7 +1403,7 @@ pub(crate) fn handle_inlay_hints(
let document_uri = &params.text_document.uri;
let FileRange { file_id, range } = from_proto::file_range(
&snap,
TextDocumentIdentifier::new(document_uri.to_owned()),
&TextDocumentIdentifier::new(document_uri.to_owned()),
params.range,
)?;
let line_index = snap.file_line_index(file_id)?;
@ -1410,17 +1412,73 @@ pub(crate) fn handle_inlay_hints(
snap.analysis
.inlay_hints(&inlay_hints_config, file_id, Some(range))?
.into_iter()
.map(|it| to_proto::inlay_hint(&snap, &line_index, it))
.map(|it| {
to_proto::inlay_hint(
&snap,
&inlay_hints_config.fields_to_resolve,
&line_index,
file_id,
it,
)
})
.collect::<Cancellable<Vec<_>>>()?,
))
}
pub(crate) fn handle_inlay_hints_resolve(
_snap: GlobalStateSnapshot,
hint: InlayHint,
snap: GlobalStateSnapshot,
mut original_hint: InlayHint,
) -> anyhow::Result<InlayHint> {
let _p = profile::span("handle_inlay_hints_resolve");
Ok(hint)
let data = match original_hint.data.take() {
Some(it) => it,
None => return Ok(original_hint),
};
let resolve_data: lsp_ext::InlayHintResolveData = serde_json::from_value(data)?;
let file_id = FileId(resolve_data.file_id);
anyhow::ensure!(snap.file_exists(file_id), "Invalid LSP resolve data");
let line_index = snap.file_line_index(file_id)?;
let range = from_proto::text_range(
&line_index,
lsp_types::Range { start: original_hint.position, end: original_hint.position },
)?;
let range_start = range.start();
let range_end = range.end();
let large_range = TextRange::new(
range_start.checked_sub(1.into()).unwrap_or(range_start),
range_end.checked_add(1.into()).unwrap_or(range_end),
);
let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints();
forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty();
let resolve_hints = snap.analysis.inlay_hints(
&forced_resolve_inlay_hints_config,
file_id,
Some(large_range),
)?;
let mut resolved_hints = resolve_hints
.into_iter()
.filter_map(|it| {
to_proto::inlay_hint(
&snap,
&forced_resolve_inlay_hints_config.fields_to_resolve,
&line_index,
file_id,
it,
)
.ok()
})
.filter(|hint| hint.position == original_hint.position)
.filter(|hint| hint.kind == original_hint.kind);
if let Some(resolved_hint) = resolved_hints.next() {
if resolved_hints.next().is_none() {
return Ok(resolved_hint);
}
}
Ok(original_hint)
}
pub(crate) fn handle_call_hierarchy_prepare(
@ -1453,7 +1511,7 @@ pub(crate) fn handle_call_hierarchy_incoming(
let item = params.item;
let doc = TextDocumentIdentifier::new(item.uri);
let frange = from_proto::file_range(&snap, doc, item.selection_range)?;
let frange = from_proto::file_range(&snap, &doc, item.selection_range)?;
let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
let call_items = match snap.analysis.incoming_calls(fpos)? {
@ -1488,7 +1546,7 @@ pub(crate) fn handle_call_hierarchy_outgoing(
let item = params.item;
let doc = TextDocumentIdentifier::new(item.uri);
let frange = from_proto::file_range(&snap, doc, item.selection_range)?;
let frange = from_proto::file_range(&snap, &doc, item.selection_range)?;
let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
let call_items = match snap.analysis.outgoing_calls(fpos)? {
@ -1569,18 +1627,21 @@ pub(crate) fn handle_semantic_tokens_full_delta(
snap.config.highlighting_non_standard_tokens(),
);
let mut cache = snap.semantic_tokens_cache.lock();
let cached_tokens = cache.entry(params.text_document.uri).or_default();
let cached_tokens = snap.semantic_tokens_cache.lock().remove(&params.text_document.uri);
if let Some(prev_id) = &cached_tokens.result_id {
if let Some(cached_tokens @ lsp_types::SemanticTokens { result_id: Some(prev_id), .. }) =
&cached_tokens
{
if *prev_id == params.previous_result_id {
let delta = to_proto::semantic_token_delta(cached_tokens, &semantic_tokens);
*cached_tokens = semantic_tokens;
snap.semantic_tokens_cache.lock().insert(params.text_document.uri, semantic_tokens);
return Ok(Some(delta.into()));
}
}
*cached_tokens = semantic_tokens.clone();
// Clone first to keep the lock short
let semantic_tokens_clone = semantic_tokens.clone();
snap.semantic_tokens_cache.lock().insert(params.text_document.uri, semantic_tokens_clone);
Ok(Some(semantic_tokens.into()))
}
@ -1591,7 +1652,7 @@ pub(crate) fn handle_semantic_tokens_range(
) -> anyhow::Result<Option<SemanticTokensRangeResult>> {
let _p = profile::span("handle_semantic_tokens_range");
let frange = from_proto::file_range(&snap, params.text_document, params.range)?;
let frange = from_proto::file_range(&snap, &params.text_document, params.range)?;
let text = snap.analysis.file_text(frange.file_id)?;
let line_index = snap.file_line_index(frange.file_id)?;
@ -1674,7 +1735,7 @@ pub(crate) fn handle_move_item(
) -> anyhow::Result<Vec<lsp_ext::SnippetTextEdit>> {
let _p = profile::span("handle_move_item");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let range = from_proto::file_range(&snap, params.text_document, params.range)?;
let range = from_proto::file_range(&snap, &params.text_document, params.range)?;
let direction = match params.direction {
lsp_ext::MoveItemDirection::Up => ide::Direction::Up,
@ -1879,12 +1940,15 @@ fn run_rustfmt(
// Determine the edition of the crate the file belongs to (if there's multiple, we pick the
// highest edition).
let editions = snap
let Ok(editions) = snap
.analysis
.relevant_crates_for(file_id)?
.into_iter()
.map(|crate_id| snap.analysis.crate_edition(crate_id))
.collect::<Result<Vec<_>, _>>()?;
.collect::<Result<Vec<_>, _>>()
else {
return Ok(None);
};
let edition = editions.iter().copied().max();
let line_index = snap.file_line_index(file_id)?;
@ -1894,23 +1958,7 @@ fn run_rustfmt(
let mut cmd = process::Command::new(toolchain::rustfmt());
cmd.envs(snap.config.extra_env());
cmd.args(extra_args);
// try to chdir to the file so we can respect `rustfmt.toml`
// FIXME: use `rustfmt --config-path` once
// https://github.com/rust-lang/rustfmt/issues/4660 gets fixed
match text_document.uri.to_file_path() {
Ok(mut path) => {
// pop off file name
if path.pop() && path.is_dir() {
cmd.current_dir(path);
}
}
Err(_) => {
tracing::error!(
"Unable to get file path for {}, rustfmt.toml might be ignored",
text_document.uri
);
}
}
if let Some(edition) = edition {
cmd.arg("--edition");
cmd.arg(edition.to_string());
@ -1929,7 +1977,7 @@ fn run_rustfmt(
.into());
}
let frange = from_proto::file_range(snap, text_document, range)?;
let frange = from_proto::file_range(snap, &text_document, range)?;
let start_line = line_index.index.line_col(frange.range.start()).line;
let end_line = line_index.index.line_col(frange.range.end()).line;
@ -1948,12 +1996,31 @@ fn run_rustfmt(
}
RustfmtConfig::CustomCommand { command, args } => {
let mut cmd = process::Command::new(command);
cmd.envs(snap.config.extra_env());
cmd.args(args);
cmd
}
};
// try to chdir to the file so we can respect `rustfmt.toml`
// FIXME: use `rustfmt --config-path` once
// https://github.com/rust-lang/rustfmt/issues/4660 gets fixed
match text_document.uri.to_file_path() {
Ok(mut path) => {
// pop off file name
if path.pop() && path.is_dir() {
command.current_dir(path);
}
}
Err(_) => {
tracing::error!(
text_document = ?text_document.uri,
"Unable to get path, rustfmt.toml might be ignored"
);
}
}
let mut rustfmt = command
.stdin(Stdio::piped())
.stdout(Stdio::piped())

View file

@ -23,18 +23,13 @@ mod cargo_target_spec;
mod diagnostics;
mod diff;
mod dispatch;
mod from_proto;
mod global_state;
mod line_index;
mod lsp_utils;
mod main_loop;
mod markdown;
mod mem_docs;
mod op_queue;
mod reload;
mod semantic_tokens;
mod task_pool;
mod to_proto;
mod version;
mod handlers {
@ -43,13 +38,12 @@ mod handlers {
}
pub mod config;
pub mod lsp_ext;
pub mod lsp;
use self::lsp::ext as lsp_ext;
#[cfg(test)]
mod integrated_benchmarks;
use std::fmt;
use serde::de::DeserializeOwned;
pub use crate::{caps::server_capabilities, main_loop::main_loop, version::version};
@ -61,23 +55,3 @@ pub fn from_json<T: DeserializeOwned>(
serde_json::from_value(json.clone())
.map_err(|e| anyhow::format_err!("Failed to deserialize {what}: {e}; {json}"))
}
#[derive(Debug)]
struct LspError {
code: i32,
message: String,
}
impl LspError {
fn new(code: i32, message: String) -> LspError {
LspError { code, message }
}
}
impl fmt::Display for LspError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Language Server request failed with {}. ({})", self.code, self.message)
}
}
impl std::error::Error for LspError {}

View file

@ -0,0 +1,29 @@
//! Custom LSP definitions and protocol conversions.
use core::fmt;
pub(crate) mod utils;
pub(crate) mod semantic_tokens;
pub mod ext;
pub(crate) mod from_proto;
pub(crate) mod to_proto;
#[derive(Debug)]
pub(crate) struct LspError {
pub(crate) code: i32,
pub(crate) message: String,
}
impl LspError {
pub(crate) fn new(code: i32, message: String) -> LspError {
LspError { code, message }
}
}
impl fmt::Display for LspError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Language Server request failed with {}. ({})", self.code, self.message)
}
}
impl std::error::Error for LspError {}

View file

@ -682,7 +682,9 @@ pub struct CompletionResolveData {
}
#[derive(Debug, Serialize, Deserialize)]
pub struct InlayHintResolveData {}
pub struct InlayHintResolveData {
pub file_id: u32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CompletionImport {

View file

@ -12,8 +12,8 @@ use crate::{
from_json,
global_state::GlobalStateSnapshot,
line_index::{LineIndex, PositionEncoding},
lsp::utils::invalid_params_error,
lsp_ext,
lsp_utils::invalid_params_error,
};
pub(crate) fn abs_path(url: &lsp_types::Url) -> anyhow::Result<AbsPathBuf> {
@ -72,7 +72,7 @@ pub(crate) fn file_position(
pub(crate) fn file_range(
snap: &GlobalStateSnapshot,
text_document_identifier: lsp_types::TextDocumentIdentifier,
text_document_identifier: &lsp_types::TextDocumentIdentifier,
range: lsp_types::Range,
) -> anyhow::Result<FileRange> {
file_range_uri(snap, &text_document_identifier.uri, range)

View file

@ -8,11 +8,12 @@ use std::{
use ide::{
Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem,
CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint,
InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory,
RenameError, Runnable, Severity, SignatureHelp, SnippetEdit, SourceChange, StructureNodeKind,
SymbolKind, TextEdit, TextRange, TextSize,
Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel,
InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup,
NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp,
SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
};
use ide_db::rust_doc::format_docs;
use itertools::Itertools;
use serde_json::to_value;
use vfs::AbsPath;
@ -22,9 +23,12 @@ use crate::{
config::{CallInfoConfig, Config},
global_state::GlobalStateSnapshot,
line_index::{LineEndings, LineIndex, PositionEncoding},
lsp::{
semantic_tokens::{self, standard_fallback_type},
utils::invalid_params_error,
LspError,
},
lsp_ext::{self, SnippetTextEdit},
lsp_utils::invalid_params_error,
semantic_tokens::{self, standard_fallback_type},
};
pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
@ -102,7 +106,7 @@ pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSe
}
pub(crate) fn documentation(documentation: Documentation) -> lsp_types::Documentation {
let value = crate::markdown::format_docs(documentation.as_str());
let value = format_docs(&documentation);
let markup_content = lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value };
lsp_types::Documentation::MarkupContent(markup_content)
}
@ -413,7 +417,7 @@ pub(crate) fn signature_help(
let documentation = call_info.doc.filter(|_| config.docs).map(|doc| {
lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value: crate::markdown::format_docs(&doc),
value: format_docs(&doc),
})
});
@ -434,10 +438,25 @@ pub(crate) fn signature_help(
pub(crate) fn inlay_hint(
snap: &GlobalStateSnapshot,
fields_to_resolve: &InlayFieldsToResolve,
line_index: &LineIndex,
file_id: FileId,
inlay_hint: InlayHint,
) -> Cancellable<lsp_types::InlayHint> {
let (label, tooltip) = inlay_hint_label(snap, inlay_hint.label)?;
let needs_resolve = inlay_hint.needs_resolve;
let (label, tooltip, mut something_to_resolve) =
inlay_hint_label(snap, fields_to_resolve, needs_resolve, inlay_hint.label)?;
let text_edits = if needs_resolve && fields_to_resolve.resolve_text_edits {
something_to_resolve |= inlay_hint.text_edit.is_some();
None
} else {
inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it))
};
let data = if needs_resolve && something_to_resolve {
Some(to_value(lsp_ext::InlayHintResolveData { file_id: file_id.0 }).unwrap())
} else {
None
};
Ok(lsp_types::InlayHint {
position: match inlay_hint.position {
@ -451,8 +470,8 @@ pub(crate) fn inlay_hint(
InlayKind::Type | InlayKind::Chaining => Some(lsp_types::InlayHintKind::TYPE),
_ => None,
},
text_edits: inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it)),
data: None,
text_edits,
data,
tooltip,
label,
})
@ -460,13 +479,18 @@ pub(crate) fn inlay_hint(
fn inlay_hint_label(
snap: &GlobalStateSnapshot,
fields_to_resolve: &InlayFieldsToResolve,
needs_resolve: bool,
mut label: InlayHintLabel,
) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>)> {
let res = match &*label.parts {
) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>, bool)> {
let mut something_to_resolve = false;
let (label, tooltip) = match &*label.parts {
[InlayHintLabelPart { linked_location: None, .. }] => {
let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap();
(
lsp_types::InlayHintLabel::String(text),
let hint_tooltip = if needs_resolve && fields_to_resolve.resolve_hint_tooltip {
something_to_resolve |= tooltip.is_some();
None
} else {
match tooltip {
Some(ide::InlayTooltip::String(s)) => {
Some(lsp_types::InlayHintTooltip::String(s))
@ -478,41 +502,52 @@ fn inlay_hint_label(
}))
}
None => None,
},
)
}
};
(lsp_types::InlayHintLabel::String(text), hint_tooltip)
}
_ => {
let parts = label
.parts
.into_iter()
.map(|part| {
part.linked_location.map(|range| location(snap, range)).transpose().map(
|location| lsp_types::InlayHintLabelPart {
value: part.text,
tooltip: match part.tooltip {
Some(ide::InlayTooltip::String(s)) => {
Some(lsp_types::InlayHintLabelPartTooltip::String(s))
}
Some(ide::InlayTooltip::Markdown(s)) => {
Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent(
lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value: s,
},
))
}
None => None,
},
location,
command: None,
},
)
let tooltip = if needs_resolve && fields_to_resolve.resolve_label_tooltip {
something_to_resolve |= part.tooltip.is_some();
None
} else {
match part.tooltip {
Some(ide::InlayTooltip::String(s)) => {
Some(lsp_types::InlayHintLabelPartTooltip::String(s))
}
Some(ide::InlayTooltip::Markdown(s)) => {
Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent(
lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value: s,
},
))
}
None => None,
}
};
let location = if needs_resolve && fields_to_resolve.resolve_label_location {
something_to_resolve |= part.linked_location.is_some();
None
} else {
part.linked_location.map(|range| location(snap, range)).transpose()?
};
Ok(lsp_types::InlayHintLabelPart {
value: part.text,
tooltip,
location,
command: None,
})
})
.collect::<Cancellable<_>>()?;
(lsp_types::InlayHintLabel::LabelParts(parts), None)
}
};
Ok(res)
Ok((label, tooltip, something_to_resolve))
}
static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1);
@ -1323,17 +1358,18 @@ pub(crate) fn code_lens(
})
}
}
AnnotationKind::HasImpls { pos: file_range, data } => {
AnnotationKind::HasImpls { pos, data } => {
if !client_commands_config.show_reference {
return Ok(());
}
let line_index = snap.file_line_index(file_range.file_id)?;
let line_index = snap.file_line_index(pos.file_id)?;
let annotation_range = range(&line_index, annotation.range);
let url = url(snap, file_range.file_id);
let url = url(snap, pos.file_id);
let pos = position(&line_index, pos.offset);
let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
let doc_pos = lsp_types::TextDocumentPositionParams::new(id, annotation_range.start);
let doc_pos = lsp_types::TextDocumentPositionParams::new(id, pos);
let goto_params = lsp_types::request::GotoImplementationParams {
text_document_position_params: doc_pos,
@ -1356,7 +1392,7 @@ pub(crate) fn code_lens(
command::show_references(
implementation_title(locations.len()),
&url,
annotation_range.start,
pos,
locations,
)
});
@ -1376,28 +1412,24 @@ pub(crate) fn code_lens(
})(),
})
}
AnnotationKind::HasReferences { pos: file_range, data } => {
AnnotationKind::HasReferences { pos, data } => {
if !client_commands_config.show_reference {
return Ok(());
}
let line_index = snap.file_line_index(file_range.file_id)?;
let line_index = snap.file_line_index(pos.file_id)?;
let annotation_range = range(&line_index, annotation.range);
let url = url(snap, file_range.file_id);
let url = url(snap, pos.file_id);
let pos = position(&line_index, pos.offset);
let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
let doc_pos = lsp_types::TextDocumentPositionParams::new(id, annotation_range.start);
let doc_pos = lsp_types::TextDocumentPositionParams::new(id, pos);
let command = data.map(|ranges| {
let locations: Vec<lsp_types::Location> =
ranges.into_iter().filter_map(|range| location(snap, range).ok()).collect();
command::show_references(
reference_title(locations.len()),
&url,
annotation_range.start,
locations,
)
command::show_references(reference_title(locations.len()), &url, pos, locations)
});
acc.push(lsp_types::CodeLens {
@ -1425,8 +1457,8 @@ pub(crate) mod command {
use crate::{
global_state::GlobalStateSnapshot,
lsp::to_proto::{location, location_link},
lsp_ext,
to_proto::{location, location_link},
};
pub(crate) fn show_references(
@ -1528,11 +1560,11 @@ pub(crate) fn markup_content(
ide::HoverDocFormat::Markdown => lsp_types::MarkupKind::Markdown,
ide::HoverDocFormat::PlainText => lsp_types::MarkupKind::PlainText,
};
let value = crate::markdown::format_docs(markup.as_str());
let value = format_docs(&Documentation::new(markup.into()));
lsp_types::MarkupContent { kind, value }
}
pub(crate) fn rename_error(err: RenameError) -> crate::LspError {
pub(crate) fn rename_error(err: RenameError) -> LspError {
// This is wrong, but we don't have a better alternative I suppose?
// https://github.com/microsoft/language-server-protocol/issues/1341
invalid_params_error(err.to_string())

View file

@ -6,10 +6,10 @@ use lsp_types::request::Request;
use triomphe::Arc;
use crate::{
from_proto,
global_state::GlobalState,
line_index::{LineEndings, LineIndex, PositionEncoding},
lsp_ext, LspError,
lsp::{from_proto, LspError},
lsp_ext,
};
pub(crate) fn invalid_params_error(message: String) -> LspError {

View file

@ -17,11 +17,14 @@ use vfs::FileId;
use crate::{
config::Config,
diagnostics::fetch_native_diagnostics,
dispatch::{NotificationDispatcher, RequestDispatcher},
from_proto,
global_state::{file_id_to_url, url_to_file_id, GlobalState},
lsp::{
from_proto,
utils::{notification_is, Progress},
},
lsp_ext,
lsp_utils::{notification_is, Progress},
reload::{BuildDataProgress, ProcMacroProgress, ProjectWorkspaceProgress},
};
@ -317,8 +320,11 @@ impl GlobalState {
}
// Refresh inlay hints if the client supports it.
if self.config.inlay_hints_refresh() {
if (self.send_hint_refresh_query || self.proc_macro_changed)
&& self.config.inlay_hints_refresh()
{
self.send_request::<lsp_types::request::InlayHintRefreshRequest>((), |_, _| ());
self.send_hint_refresh_query = false;
}
}
@ -420,6 +426,32 @@ impl GlobalState {
});
}
fn update_diagnostics(&mut self) {
let db = self.analysis_host.raw_database();
let subscriptions = self
.mem_docs
.iter()
.map(|path| self.vfs.read().0.file_id(path).unwrap())
.filter(|&file_id| {
let source_root = db.file_source_root(file_id);
// Only publish diagnostics for files in the workspace, not from crates.io deps
// or the sysroot.
// While theoretically these should never have errors, we have quite a few false
// positives particularly in the stdlib, and those diagnostics would stay around
// forever if we emitted them here.
!db.source_root(source_root).is_library
})
.collect::<Vec<_>>();
tracing::trace!("updating notifications for {:?}", subscriptions);
// Diagnostics are triggered by the user typing
// so we run them on a latency sensitive thread.
self.task_pool.handle.spawn(ThreadIntent::LatencySensitive, {
let snapshot = self.snapshot();
move || Task::Diagnostics(fetch_native_diagnostics(snapshot, subscriptions))
});
}
fn update_status_or_notify(&mut self) {
let status = self.current_status();
if self.last_reported_status.as_ref() != Some(&status) {
@ -509,6 +541,7 @@ impl GlobalState {
}
self.switch_workspaces("fetched build data".to_string());
self.send_hint_refresh_query = true;
(Some(Progress::End), None)
}
@ -525,7 +558,7 @@ impl GlobalState {
ProcMacroProgress::End(proc_macro_load_result) => {
self.fetch_proc_macros_queue.op_completed(true);
self.set_proc_macros(proc_macro_load_result);
self.send_hint_refresh_query = true;
(Some(Progress::End), None)
}
};
@ -670,6 +703,7 @@ impl GlobalState {
}
use crate::handlers::request as handlers;
use lsp_types::request as lsp_request;
dispatcher
// Request handlers that must run on the main thread
@ -682,30 +716,30 @@ impl GlobalState {
// are run on the main thread to reduce latency:
.on_sync::<lsp_ext::JoinLines>(handlers::handle_join_lines)
.on_sync::<lsp_ext::OnEnter>(handlers::handle_on_enter)
.on_sync::<lsp_types::request::SelectionRangeRequest>(handlers::handle_selection_range)
.on_sync::<lsp_request::SelectionRangeRequest>(handlers::handle_selection_range)
.on_sync::<lsp_ext::MatchingBrace>(handlers::handle_matching_brace)
.on_sync::<lsp_ext::OnTypeFormatting>(handlers::handle_on_type_formatting)
// Formatting should be done immediately as the editor might wait on it, but we can't
// put it on the main thread as we do not want the main thread to block on rustfmt.
// So we have an extra thread just for formatting requests to make sure it gets handled
// as fast as possible.
.on_fmt_thread::<lsp_types::request::Formatting>(handlers::handle_formatting)
.on_fmt_thread::<lsp_types::request::RangeFormatting>(handlers::handle_range_formatting)
.on_fmt_thread::<lsp_request::Formatting>(handlers::handle_formatting)
.on_fmt_thread::<lsp_request::RangeFormatting>(handlers::handle_range_formatting)
// We cant run latency-sensitive request handlers which do semantic
// analysis on the main thread because that would block other
// requests. Instead, we run these request handlers on higher priority
// threads in the threadpool.
.on_latency_sensitive::<lsp_types::request::Completion>(handlers::handle_completion)
.on_latency_sensitive::<lsp_types::request::ResolveCompletionItem>(
.on_latency_sensitive::<lsp_request::Completion>(handlers::handle_completion)
.on_latency_sensitive::<lsp_request::ResolveCompletionItem>(
handlers::handle_completion_resolve,
)
.on_latency_sensitive::<lsp_types::request::SemanticTokensFullRequest>(
.on_latency_sensitive::<lsp_request::SemanticTokensFullRequest>(
handlers::handle_semantic_tokens_full,
)
.on_latency_sensitive::<lsp_types::request::SemanticTokensFullDeltaRequest>(
.on_latency_sensitive::<lsp_request::SemanticTokensFullDeltaRequest>(
handlers::handle_semantic_tokens_full_delta,
)
.on_latency_sensitive::<lsp_types::request::SemanticTokensRangeRequest>(
.on_latency_sensitive::<lsp_request::SemanticTokensRangeRequest>(
handlers::handle_semantic_tokens_range,
)
// All other request handlers
@ -729,29 +763,25 @@ impl GlobalState {
.on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
.on::<lsp_ext::MoveItem>(handlers::handle_move_item)
.on::<lsp_ext::WorkspaceSymbol>(handlers::handle_workspace_symbol)
.on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)
.on::<lsp_types::request::GotoDefinition>(handlers::handle_goto_definition)
.on::<lsp_types::request::GotoDeclaration>(handlers::handle_goto_declaration)
.on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)
.on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)
.on_no_retry::<lsp_types::request::InlayHintRequest>(handlers::handle_inlay_hints)
.on::<lsp_types::request::InlayHintResolveRequest>(handlers::handle_inlay_hints_resolve)
.on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)
.on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)
.on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)
.on::<lsp_types::request::SignatureHelpRequest>(handlers::handle_signature_help)
.on::<lsp_types::request::PrepareRenameRequest>(handlers::handle_prepare_rename)
.on::<lsp_types::request::Rename>(handlers::handle_rename)
.on::<lsp_types::request::References>(handlers::handle_references)
.on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight)
.on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)
.on::<lsp_types::request::CallHierarchyIncomingCalls>(
handlers::handle_call_hierarchy_incoming,
)
.on::<lsp_types::request::CallHierarchyOutgoingCalls>(
handlers::handle_call_hierarchy_outgoing,
)
.on::<lsp_types::request::WillRenameFiles>(handlers::handle_will_rename_files)
.on::<lsp_request::DocumentSymbolRequest>(handlers::handle_document_symbol)
.on::<lsp_request::GotoDefinition>(handlers::handle_goto_definition)
.on::<lsp_request::GotoDeclaration>(handlers::handle_goto_declaration)
.on::<lsp_request::GotoImplementation>(handlers::handle_goto_implementation)
.on::<lsp_request::GotoTypeDefinition>(handlers::handle_goto_type_definition)
.on_no_retry::<lsp_request::InlayHintRequest>(handlers::handle_inlay_hints)
.on::<lsp_request::InlayHintResolveRequest>(handlers::handle_inlay_hints_resolve)
.on::<lsp_request::CodeLensRequest>(handlers::handle_code_lens)
.on::<lsp_request::CodeLensResolve>(handlers::handle_code_lens_resolve)
.on::<lsp_request::FoldingRangeRequest>(handlers::handle_folding_range)
.on::<lsp_request::SignatureHelpRequest>(handlers::handle_signature_help)
.on::<lsp_request::PrepareRenameRequest>(handlers::handle_prepare_rename)
.on::<lsp_request::Rename>(handlers::handle_rename)
.on::<lsp_request::References>(handlers::handle_references)
.on::<lsp_request::DocumentHighlightRequest>(handlers::handle_document_highlight)
.on::<lsp_request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)
.on::<lsp_request::CallHierarchyIncomingCalls>(handlers::handle_call_hierarchy_incoming)
.on::<lsp_request::CallHierarchyOutgoingCalls>(handlers::handle_call_hierarchy_outgoing)
.on::<lsp_request::WillRenameFiles>(handlers::handle_will_rename_files)
.on::<lsp_ext::Ssr>(handlers::handle_ssr)
.on::<lsp_ext::ViewRecursiveMemoryLayout>(handlers::handle_view_recursive_memory_layout)
.finish();
@ -788,77 +818,4 @@ impl GlobalState {
.finish();
Ok(())
}
fn update_diagnostics(&mut self) {
let db = self.analysis_host.raw_database();
let subscriptions = self
.mem_docs
.iter()
.map(|path| self.vfs.read().0.file_id(path).unwrap())
.filter(|&file_id| {
let source_root = db.file_source_root(file_id);
// Only publish diagnostics for files in the workspace, not from crates.io deps
// or the sysroot.
// While theoretically these should never have errors, we have quite a few false
// positives particularly in the stdlib, and those diagnostics would stay around
// forever if we emitted them here.
!db.source_root(source_root).is_library
})
.collect::<Vec<_>>();
tracing::trace!("updating notifications for {:?}", subscriptions);
let snapshot = self.snapshot();
// Diagnostics are triggered by the user typing
// so we run them on a latency sensitive thread.
self.task_pool.handle.spawn(ThreadIntent::LatencySensitive, move || {
let _p = profile::span("publish_diagnostics");
let _ctx = stdx::panic_context::enter("publish_diagnostics".to_owned());
let diagnostics = subscriptions
.into_iter()
.filter_map(|file_id| {
let line_index = snapshot.file_line_index(file_id).ok()?;
Some((
file_id,
line_index,
snapshot
.analysis
.diagnostics(
&snapshot.config.diagnostics(),
ide::AssistResolveStrategy::None,
file_id,
)
.ok()?,
))
})
.map(|(file_id, line_index, it)| {
(
file_id,
it.into_iter()
.map(move |d| lsp_types::Diagnostic {
range: crate::to_proto::range(&line_index, d.range),
severity: Some(crate::to_proto::diagnostic_severity(d.severity)),
code: Some(lsp_types::NumberOrString::String(
d.code.as_str().to_string(),
)),
code_description: Some(lsp_types::CodeDescription {
href: lsp_types::Url::parse(&d.code.url()).unwrap(),
}),
source: Some("rust-analyzer".to_string()),
message: d.message,
related_information: None,
tags: if d.unused {
Some(vec![lsp_types::DiagnosticTag::UNNECESSARY])
} else {
None
},
data: None,
})
.collect::<Vec<_>>(),
)
});
Task::Diagnostics(diagnostics.collect())
});
}
}

View file

@ -1,165 +0,0 @@
//! Transforms markdown
use ide_db::rust_doc::is_rust_fence;
const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"];
pub(crate) fn format_docs(src: &str) -> String {
let mut processed_lines = Vec::new();
let mut in_code_block = false;
let mut is_rust = false;
for mut line in src.lines() {
if in_code_block && is_rust && code_line_ignored_by_rustdoc(line) {
continue;
}
if let Some(header) = RUSTDOC_FENCES.into_iter().find_map(|fence| line.strip_prefix(fence))
{
in_code_block ^= true;
if in_code_block {
is_rust = is_rust_fence(header);
if is_rust {
line = "```rust";
}
}
}
if in_code_block {
let trimmed = line.trim_start();
if is_rust && trimmed.starts_with("##") {
line = &trimmed[1..];
}
}
processed_lines.push(line);
}
processed_lines.join("\n")
}
fn code_line_ignored_by_rustdoc(line: &str) -> bool {
let trimmed = line.trim();
trimmed == "#" || trimmed.starts_with("# ") || trimmed.starts_with("#\t")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_docs_adds_rust() {
let comment = "```\nfn some_rust() {}\n```";
assert_eq!(format_docs(comment), "```rust\nfn some_rust() {}\n```");
}
#[test]
fn test_format_docs_handles_plain_text() {
let comment = "```text\nthis is plain text\n```";
assert_eq!(format_docs(comment), "```text\nthis is plain text\n```");
}
#[test]
fn test_format_docs_handles_non_rust() {
let comment = "```sh\nsupposedly shell code\n```";
assert_eq!(format_docs(comment), "```sh\nsupposedly shell code\n```");
}
#[test]
fn test_format_docs_handles_rust_alias() {
let comment = "```ignore\nlet z = 55;\n```";
assert_eq!(format_docs(comment), "```rust\nlet z = 55;\n```");
}
#[test]
fn test_format_docs_handles_complex_code_block_attrs() {
let comment = "```rust,no_run\nlet z = 55;\n```";
assert_eq!(format_docs(comment), "```rust\nlet z = 55;\n```");
}
#[test]
fn test_format_docs_handles_error_codes() {
let comment = "```compile_fail,E0641\nlet b = 0 as *const _;\n```";
assert_eq!(format_docs(comment), "```rust\nlet b = 0 as *const _;\n```");
}
#[test]
fn test_format_docs_skips_comments_in_rust_block() {
let comment =
"```rust\n # skip1\n# skip2\n#stay1\nstay2\n#\n #\n # \n #\tskip3\n\t#\t\n```";
assert_eq!(format_docs(comment), "```rust\n#stay1\nstay2\n```");
}
#[test]
fn test_format_docs_does_not_skip_lines_if_plain_text() {
let comment =
"```text\n # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t\n```";
assert_eq!(
format_docs(comment),
"```text\n # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t\n```",
);
}
#[test]
fn test_format_docs_keeps_comments_outside_of_rust_block() {
let comment = " # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t";
assert_eq!(format_docs(comment), comment);
}
#[test]
fn test_format_docs_preserves_newlines() {
let comment = "this\nis\nmultiline";
assert_eq!(format_docs(comment), comment);
}
#[test]
fn test_code_blocks_in_comments_marked_as_rust() {
let comment = r#"```rust
fn main(){}
```
Some comment.
```
let a = 1;
```"#;
assert_eq!(
format_docs(comment),
"```rust\nfn main(){}\n```\nSome comment.\n```rust\nlet a = 1;\n```"
);
}
#[test]
fn test_code_blocks_in_comments_marked_as_text() {
let comment = r#"```text
filler
text
```
Some comment.
```
let a = 1;
```"#;
assert_eq!(
format_docs(comment),
"```text\nfiller\ntext\n```\nSome comment.\n```rust\nlet a = 1;\n```"
);
}
#[test]
fn test_format_docs_handles_escape_double_hashes() {
let comment = r#"```rust
let s = "foo
## bar # baz";
```"#;
assert_eq!(format_docs(comment), "```rust\nlet s = \"foo\n# bar # baz\";\n```");
}
#[test]
fn test_format_docs_handles_double_hashes_non_rust() {
let comment = r#"```markdown
## A second-level heading
```"#;
assert_eq!(format_docs(comment), "```markdown\n## A second-level heading\n```");
}
}

View file

@ -12,6 +12,7 @@
//! correct. Instead, we try to provide a best-effort service. Even if the
//! project is currently loading and we don't have a full project model, we
//! still want to respond to various requests.
// FIXME: This is a mess that needs some untangling work
use std::{iter, mem};
use flycheck::{FlycheckConfig, FlycheckHandle};

View file

@ -29,7 +29,7 @@ use lsp_types::{
PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem,
TextDocumentPositionParams, WorkDoneProgressParams,
};
use rust_analyzer::lsp_ext::{OnEnter, Runnables, RunnablesParams};
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams};
use serde_json::json;
use test_utils::skip_slow_tests;
@ -861,6 +861,7 @@ edition = "2021"
bar = {path = "../bar"}
//- /foo/src/main.rs
#![allow(internal_features)]
#![feature(rustc_attrs, decl_macro)]
use bar::Bar;
@ -938,7 +939,7 @@ pub fn foo(_input: TokenStream) -> TokenStream {
let res = server.send_request::<HoverRequest>(HoverParams {
text_document_position_params: TextDocumentPositionParams::new(
server.doc_id("foo/src/main.rs"),
Position::new(11, 9),
Position::new(12, 9),
),
work_done_progress_params: Default::default(),
});

View file

@ -9,7 +9,7 @@ use std::{
use crossbeam_channel::{after, select, Receiver};
use lsp_server::{Connection, Message, Notification, Request};
use lsp_types::{notification::Exit, request::Shutdown, TextDocumentIdentifier, Url};
use rust_analyzer::{config::Config, lsp_ext, main_loop};
use rust_analyzer::{config::Config, lsp, main_loop};
use serde::Serialize;
use serde_json::{json, to_string_pretty, Value};
use test_utils::FixtureWithProjectMeta;
@ -260,9 +260,9 @@ impl Server {
Message::Notification(n) if n.method == "experimental/serverStatus" => {
let status = n
.clone()
.extract::<lsp_ext::ServerStatusParams>("experimental/serverStatus")
.extract::<lsp::ext::ServerStatusParams>("experimental/serverStatus")
.unwrap();
if status.health != lsp_ext::Health::Ok {
if status.health != lsp::ext::Health::Ok {
panic!("server errored/warned while loading workspace: {:?}", status.message);
}
status.quiescent

View file

@ -35,7 +35,7 @@ fn check_lsp_extensions_docs() {
let expected_hash = {
let lsp_ext_rs = sh
.read_file(sourcegen::project_root().join("crates/rust-analyzer/src/lsp_ext.rs"))
.read_file(sourcegen::project_root().join("crates/rust-analyzer/src/lsp/ext.rs"))
.unwrap();
stable_hash(lsp_ext_rs.as_str())
};
@ -45,7 +45,7 @@ fn check_lsp_extensions_docs() {
sh.read_file(sourcegen::project_root().join("docs/dev/lsp-extensions.md")).unwrap();
let text = lsp_extensions_md
.lines()
.find_map(|line| line.strip_prefix("lsp_ext.rs hash:"))
.find_map(|line| line.strip_prefix("lsp/ext.rs hash:"))
.unwrap()
.trim();
u64::from_str_radix(text, 16).unwrap()
@ -54,7 +54,7 @@ fn check_lsp_extensions_docs() {
if actual_hash != expected_hash {
panic!(
"
lsp_ext.rs was changed without touching lsp-extensions.md.
lsp/ext.rs was changed without touching lsp-extensions.md.
Expected hash: {expected_hash:x}
Actual hash: {actual_hash:x}
@ -158,7 +158,7 @@ Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT
Apache-2.0/MIT
BSD-3-Clause
BlueOak-1.0.0 OR MIT OR Apache-2.0
CC0-1.0 OR Artistic-2.0
CC0-1.0
ISC
MIT
MIT / Apache-2.0