@@ -19,7 +19,7 @@ pub mod theme;
1919pub mod tree;
2020pub mod view;
2121
22- use std:: num:: NonZeroUsize ;
22+ use std:: { borrow :: Cow , num:: NonZeroUsize , path :: Path } ;
2323
2424// uses NonZeroUsize so Option<DocumentId> use a byte rather than two
2525#[ derive( Clone , Copy , PartialEq , Eq , PartialOrd , Ord , Hash , Debug ) ]
@@ -73,8 +73,164 @@ pub fn align_view(doc: &mut Document, view: &View, align: Align) {
7373 doc. set_view_offset ( view. id , view_offset) ;
7474}
7575
76+ /// Returns the left-side position of the primary selection.
77+ pub fn primary_cursor ( view : & View , doc : & Document ) -> usize {
78+ doc. selection ( view. id )
79+ . primary ( )
80+ . cursor ( doc. text ( ) . slice ( ..) )
81+ }
82+
83+ /// Returns the next diagnostic in the document if any.
84+ ///
85+ /// This does not wrap-around.
86+ pub fn next_diagnostic_in_doc < ' d > (
87+ view : & View ,
88+ doc : & ' d Document ,
89+ severity_filter : Option < helix_core:: diagnostic:: Severity > ,
90+ ) -> Option < & ' d Diagnostic > {
91+ let cursor = primary_cursor ( view, doc) ;
92+ doc. diagnostics ( )
93+ . iter ( )
94+ . filter ( |diagnostic| diagnostic. severity >= severity_filter)
95+ . find ( |diag| diag. range . start > cursor)
96+ }
97+
98+ /// Returns the previous diagnostic in the document if any.
99+ ///
100+ /// This does not wrap-around.
101+ pub fn prev_diagnostic_in_doc < ' d > (
102+ view : & View ,
103+ doc : & ' d Document ,
104+ severity_filter : Option < helix_core:: diagnostic:: Severity > ,
105+ ) -> Option < & ' d Diagnostic > {
106+ let cursor = primary_cursor ( view, doc) ;
107+ doc. diagnostics ( )
108+ . iter ( )
109+ . rev ( )
110+ . filter ( |diagnostic| diagnostic. severity >= severity_filter)
111+ . find ( |diag| diag. range . start < cursor)
112+ }
113+
114+ pub struct WorkspaceDiagnostic < ' e > {
115+ pub path : Cow < ' e , Path > ,
116+ pub diagnostic : Cow < ' e , helix_lsp:: lsp:: Diagnostic > ,
117+ pub offset_encoding : OffsetEncoding ,
118+ }
119+ impl < ' e > WorkspaceDiagnostic < ' e > {
120+ pub fn into_owned ( self ) -> WorkspaceDiagnostic < ' static > {
121+ WorkspaceDiagnostic {
122+ path : Cow :: Owned ( self . path . into_owned ( ) ) ,
123+ diagnostic : Cow :: Owned ( self . diagnostic . into_owned ( ) ) ,
124+ offset_encoding : self . offset_encoding ,
125+ }
126+ }
127+ }
128+
129+ fn workspace_diagnostics (
130+ editor : & Editor ,
131+ severity_filter : Option < helix_core:: diagnostic:: Severity > ,
132+ ) -> impl Iterator < Item = WorkspaceDiagnostic < ' _ > > {
133+ editor
134+ . diagnostics
135+ . iter ( )
136+ . filter_map ( |( uri, diagnostics) | {
137+ // Extract Path from diagnostic Uri, skipping diagnostics that don't have a path.
138+ uri. as_path ( ) . map ( |p| ( p, diagnostics) )
139+ } )
140+ . flat_map ( |( path, diagnostics) | {
141+ let mut diagnostics = diagnostics. iter ( ) . collect :: < Vec < _ > > ( ) ;
142+ diagnostics. sort_by_key ( |( diagnostic, _) | diagnostic. range . start ) ;
143+
144+ diagnostics
145+ . into_iter ( )
146+ . map ( move |( diagnostic, diagnostic_provider) | {
147+ ( path, diagnostic, diagnostic_provider)
148+ } )
149+ } )
150+ . filter ( move |( _, diagnostic, _) | {
151+ // Filter by severity
152+ let severity = diagnostic
153+ . severity
154+ . and_then ( Document :: lsp_severity_to_severity) ;
155+ severity >= severity_filter
156+ } )
157+ . map ( |( path, diag, diagnostic_provider) | {
158+ match diagnostic_provider {
159+ DiagnosticProvider :: Lsp { server_id, .. } => {
160+ // Map language server ID to offset encoding
161+ let offset_encoding = editor
162+ . language_server_by_id ( * server_id)
163+ . map ( |client| client. offset_encoding ( ) )
164+ . unwrap_or_default ( ) ;
165+ ( path, diag, offset_encoding)
166+ }
167+ }
168+ } )
169+ . map ( |( path, diagnostic, offset_encoding) | WorkspaceDiagnostic {
170+ path : Cow :: Borrowed ( path) ,
171+ diagnostic : Cow :: Borrowed ( diagnostic) ,
172+ offset_encoding,
173+ } )
174+ }
175+
176+ pub fn first_diagnostic_in_workspace (
177+ editor : & Editor ,
178+ severity_filter : Option < helix_core:: diagnostic:: Severity > ,
179+ ) -> Option < WorkspaceDiagnostic > {
180+ workspace_diagnostics ( editor, severity_filter) . next ( )
181+ }
182+
183+ pub fn next_diagnostic_in_workspace (
184+ editor : & Editor ,
185+ severity_filter : Option < helix_core:: diagnostic:: Severity > ,
186+ ) -> Option < WorkspaceDiagnostic > {
187+ let ( view, doc) = current_ref ! ( editor) ;
188+
189+ let Some ( current_doc_path) = doc. path ( ) else {
190+ return first_diagnostic_in_workspace ( editor, severity_filter) ;
191+ } ;
192+
193+ let cursor = primary_cursor ( view, doc) ;
194+
195+ #[ allow( clippy:: filter_next) ]
196+ workspace_diagnostics ( editor, severity_filter)
197+ . filter ( |d| {
198+ // Skip diagnostics before the current document
199+ d. path >= current_doc_path. as_path ( )
200+ } )
201+ . filter ( |d| {
202+ // Skip diagnostics before the primary cursor in the current document
203+ if d. path == current_doc_path. as_path ( ) {
204+ let Some ( start) = helix_lsp:: util:: lsp_pos_to_pos (
205+ doc. text ( ) ,
206+ d. diagnostic . range . start ,
207+ d. offset_encoding ,
208+ ) else {
209+ return false ;
210+ } ;
211+ if start <= cursor {
212+ return false ;
213+ }
214+ }
215+ true
216+ } )
217+ . next ( )
218+ }
219+
220+ pub fn ensure_selections_forward ( view : & View , doc : & mut Document ) {
221+ let selection = doc
222+ . selection ( view. id )
223+ . clone ( )
224+ . transform ( |r| r. with_direction ( Direction :: Forward ) ) ;
225+
226+ doc. set_selection ( view. id , selection) ;
227+ }
228+
76229pub use document:: Document ;
77230pub use editor:: Editor ;
78- use helix_core:: char_idx_at_visual_offset;
231+ use helix_core:: {
232+ char_idx_at_visual_offset, diagnostic:: DiagnosticProvider , movement:: Direction , Diagnostic ,
233+ } ;
234+ use helix_lsp:: OffsetEncoding ;
79235pub use theme:: Theme ;
80236pub use view:: View ;
0 commit comments