11use lsp_server:: ErrorCode ;
22use lsp_types:: notification:: PublishDiagnostics ;
33use lsp_types:: {
4- Diagnostic , DiagnosticRelatedInformation , DiagnosticSeverity , DiagnosticTag , NumberOrString ,
5- PublishDiagnosticsParams , Range , Url ,
4+ CodeDescription , Diagnostic , DiagnosticRelatedInformation , DiagnosticSeverity , DiagnosticTag ,
5+ NumberOrString , PublishDiagnosticsParams , Range , Url ,
66} ;
7+ use rustc_hash:: FxHashMap ;
78
89use ruff_db:: diagnostic:: { Annotation , Severity , SubDiagnostic } ;
910use ruff_db:: files:: FileRange ;
1011use ruff_db:: source:: { line_index, source_text} ;
1112use ty_project:: { Db , ProjectDatabase } ;
1213
13- use crate :: DocumentSnapshot ;
14- use crate :: PositionEncoding ;
1514use crate :: document:: { FileRangeExt , ToRangeExt } ;
1615use crate :: server:: Result ;
1716use crate :: server:: client:: Notifier ;
17+ use crate :: { DocumentSnapshot , PositionEncoding } ;
1818
1919use super :: LSPResult ;
2020
21+ /// A series of diagnostics across a single text document or an arbitrary number of notebook cells.
22+ pub ( super ) type DiagnosticsMap = FxHashMap < Url , Vec < Diagnostic > > ;
23+
24+ /// Clears the diagnostics for the document at `uri`.
25+ ///
26+ /// This is done by notifying the client with an empty list of diagnostics for the document.
2127pub ( super ) fn clear_diagnostics ( uri : & Url , notifier : & Notifier ) -> Result < ( ) > {
2228 notifier
2329 . notify :: < PublishDiagnostics > ( PublishDiagnosticsParams {
@@ -29,31 +35,91 @@ pub(super) fn clear_diagnostics(uri: &Url, notifier: &Notifier) -> Result<()> {
2935 Ok ( ( ) )
3036}
3137
38+ /// Publishes the diagnostics for the given document snapshot using the [publish diagnostics
39+ /// notification].
40+ ///
41+ /// [publish diagnostics notification]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics
42+ pub ( super ) fn publish_diagnostics_for_document (
43+ db : & ProjectDatabase ,
44+ snapshot : & DocumentSnapshot ,
45+ notifier : & Notifier ,
46+ ) -> Result < ( ) > {
47+ for ( uri, diagnostics) in compute_diagnostics ( db, snapshot) {
48+ notifier
49+ . notify :: < PublishDiagnostics > ( PublishDiagnosticsParams {
50+ uri,
51+ diagnostics,
52+ version : Some ( snapshot. query ( ) . version ( ) ) ,
53+ } )
54+ . with_failure_code ( lsp_server:: ErrorCode :: InternalError ) ?;
55+ }
56+
57+ Ok ( ( ) )
58+ }
59+
3260pub ( super ) fn compute_diagnostics (
3361 db : & ProjectDatabase ,
3462 snapshot : & DocumentSnapshot ,
35- ) -> Vec < Diagnostic > {
63+ ) -> DiagnosticsMap {
3664 let Some ( file) = snapshot. file ( db) else {
3765 tracing:: info!(
3866 "No file found for snapshot for `{}`" ,
3967 snapshot. query( ) . file_url( )
4068 ) ;
41- return vec ! [ ] ;
69+ return DiagnosticsMap :: default ( ) ;
4270 } ;
4371
4472 let diagnostics = match db. check_file ( file) {
4573 Ok ( diagnostics) => diagnostics,
4674 Err ( cancelled) => {
4775 tracing:: info!( "Diagnostics computation {cancelled}" ) ;
48- return vec ! [ ] ;
76+ return DiagnosticsMap :: default ( ) ;
4977 }
5078 } ;
5179
52- diagnostics
53- . as_slice ( )
54- . iter ( )
55- . map ( |message| to_lsp_diagnostic ( db, message, snapshot. encoding ( ) ) )
56- . collect ( )
80+ let mut diagnostics_map = DiagnosticsMap :: default ( ) ;
81+ let query = snapshot. query ( ) ;
82+ // let source_kind = query.make_source_kind();
83+
84+ // Populates all relevant URLs with an empty diagnostic list.
85+ // This ensures that documents without diagnostics still get updated.
86+ if let Some ( notebook) = query. as_notebook ( ) {
87+ for url in notebook. urls ( ) {
88+ diagnostics_map. entry ( url. clone ( ) ) . or_default ( ) ;
89+ }
90+ } else {
91+ diagnostics_map
92+ . entry ( query. make_key ( ) . into_url ( ) )
93+ . or_default ( ) ;
94+ }
95+
96+ let lsp_diagnostics = diagnostics. as_slice ( ) . iter ( ) . map ( |diagnostic| {
97+ (
98+ // TODO: Use the cell index instead using `source_kind`
99+ usize:: default ( ) ,
100+ to_lsp_diagnostic ( db, diagnostic, snapshot. encoding ( ) ) ,
101+ )
102+ } ) ;
103+
104+ if let Some ( notebook) = query. as_notebook ( ) {
105+ for ( index, diagnostic) in lsp_diagnostics {
106+ let Some ( uri) = notebook. cell_uri_by_index ( index) else {
107+ tracing:: warn!( "Unable to find notebook cell at index {index}" ) ;
108+ continue ;
109+ } ;
110+ diagnostics_map
111+ . entry ( uri. clone ( ) )
112+ . or_default ( )
113+ . push ( diagnostic) ;
114+ }
115+ } else {
116+ diagnostics_map
117+ . entry ( query. make_key ( ) . into_url ( ) )
118+ . or_default ( )
119+ . extend ( lsp_diagnostics. map ( |( _, diagnostic) | diagnostic) ) ;
120+ }
121+
122+ diagnostics_map
57123}
58124
59125/// Converts the tool specific [`Diagnostic`][ruff_db::diagnostic::Diagnostic] to an LSP
@@ -97,9 +163,8 @@ fn to_lsp_diagnostic(
97163 . id ( )
98164 . is_lint ( )
99165 . then ( || {
100- Some ( lsp_types:: CodeDescription {
101- href : lsp_types:: Url :: parse ( & format ! ( "https://ty.dev/rules#{}" , diagnostic. id( ) ) )
102- . ok ( ) ?,
166+ Some ( CodeDescription {
167+ href : Url :: parse ( & format ! ( "https://ty.dev/rules#{}" , diagnostic. id( ) ) ) . ok ( ) ?,
103168 } )
104169 } )
105170 . flatten ( ) ;
0 commit comments