@@ -29,10 +29,10 @@ use tui::{
2929 backend::CrosstermBackend,
3030 buffer::Buffer as Surface,
3131 layout::Rect,
32− style::{Color, Style},
32+ style::{Color, Modifier, Style},
3333};
3434
35−const OFFSET: u16 = 6; // 5 linenr + 1 gutter
35+const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
3636
3737type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
3838
@@ -205,6 +205,16 @@ impl Renderer {
205205 style
206206 };
207207
208+ // ugh, improve with a traverse method
209+ // or interleave highlight spans with selection and diagnostic spans
210+ let style = if view.state.diagnostics.iter().any(|diagnostic| {
211+ diagnostic.range.0 <= char_index && diagnostic.range.1 > char_index
212+ }) {
213+ style.clone().add_modifier(Modifier::UNDERLINED)
214+ } else {
215+ style
216+ };
217+
208218 // TODO: paint cursor heads except primary
209219
210220 self.surface
@@ -212,18 +222,23 @@ impl Renderer {
212222
213223 visual_x += width;
214224 }
215− // if grapheme == "\t"
216225
217226 char_index += 1;
218227 }
219228 }
220229 }
221230 }
231+
222232 let style: Style = theme.get("ui.linenr");
233+ let warning: Style = theme.get("warning");
223234 let last_line = view.last_line();
224235 for (i, line) in (view.first_line..last_line).enumerate() {
236+ if view.state.diagnostics.iter().any(|d| d.line == line) {
237+ self.surface.set_stringn(0, i as u16, "●", 1, warning);
238+ }
239+
225240 self.surface
226− .set_stringn(0, i as u16, format!("{:>5}", line + 1), 5, style);
241+ .set_stringn(1, i as u16, format!("{:>5}", line + 1), 5, style);
227242 }
228243 }
229244
@@ -240,6 +255,13 @@ impl Renderer {
240255 );
241256 self.surface
242257 .set_string(1, self.size.1 - 2, mode, self.text_color);
258+
259+ self.surface.set_string(
260+ self.size.0 - 10,
261+ self.size.1 - 2,
262+ format!("{}", view.state.diagnostics.len()),
263+ self.text_color,
264+ );
243265 }
244266
245267 pub fn render_prompt(&mut self, view: &View, prompt: &Prompt, theme: &Theme) {
@@ -545,7 +567,59 @@ impl<'a> Application<'a> {
545567 pub async fn handle_lsp_notification(&mut self, notification: Option<helix_lsp::Notification>) {
546568 use helix_lsp::Notification;
547569 match notification {
548− Some(Notification::PublishDiagnostics(params)) => unimplemented!("{:?}", params),
570+ Some(Notification::PublishDiagnostics(params)) => {
571+ let view = self.editor.views.iter_mut().find(|view| {
572+ let path = view
573+ .state
574+ .path
575+ .as_ref()
576+ .map(|path| helix_lsp::Url::from_file_path(path).unwrap());
577+
578+ eprintln!("{:?} {} {}", path, params.uri, params.diagnostics.len());
579+ // HAXX
580+ path == Some(params.uri.clone())
581+ });
582+
583+ fn lsp_pos_to_pos(doc: &helix_core::RopeSlice, pos: helix_lsp::Position) -> usize {
584+ let line = doc.line_to_char(pos.line as usize);
585+ let line_start = doc.char_to_utf16_cu(line);
586+ doc.utf16_cu_to_char(pos.character as usize + line_start)
587+ }
588+
589+ if let Some(view) = view {
590+ let doc = view.state.doc().slice(..);
591+ let diagnostics = params
592+ .diagnostics
593+ .into_iter()
594+ .map(|diagnostic| {
595+ let start = lsp_pos_to_pos(&doc, diagnostic.range.start);
596+ let end = lsp_pos_to_pos(&doc, diagnostic.range.end);
597+
598+ // eprintln!(
599+ // "{:?}-{:?} {}-{} {}",
600+ // diagnostic.range.start,
601+ // diagnostic.range.end,
602+ // start,
603+ // end,
604+ // diagnostic.message
605+ // );
606+
607+ helix_core::Diagnostic {
608+ range: (start, end),
609+ line: diagnostic.range.start.line as usize,
610+ message: diagnostic.message,
611+ // severity
612+ // code
613+ // source
614+ }
615+ })
616+ .collect();
617+
618+ view.state.diagnostics = diagnostics;
619+
620+ self.render();
621+ }
622+ }
549623 _ => unreachable!(),
550624 }
551625 }