@@ -17,7 +17,7 @@ use helix_core::{
1717};
1818use helix_view::{
1919 document::{Mode, SCRATCH_BUFFER_NAME},
20− editor::LineNumber,
20+ editor::{Config, LineNumber},
2121 graphics::{CursorKind, Modifier, Rect, Style},
2222 info::Info,
2323 input::KeyEvent,
@@ -412,22 +412,6 @@ impl EditorView {
412412 let text = doc.text().slice(..);
413413 let last_line = view.last_line(doc);
414414
415− let linenr = theme.get("ui.linenr");
416− let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr);
417−
418− let warning = theme.get("warning");
419− let error = theme.get("error");
420− let info = theme.get("info");
421− let hint = theme.get("hint");
422−
423− // Whether to draw the line number for the last line of the
424− // document or not. We only draw it if it's not an empty line.
425− let draw_last = text.line_to_byte(last_line) < text.len_bytes();
426−
427− let current_line = doc
428− .text()
429− .char_to_line(doc.selection(view.id).primary().cursor(text));
430−
431415 // it's used inside an iterator so the collect isn't needless:
432416 // https://github.com/rust-lang/rust-clippy/issues/6164
433417 #[allow(clippy::needless_collect)]
@@ -437,51 +421,99 @@ impl EditorView {
437421 .map(|range| range.cursor_line(text))
438422 .collect();
439423
440− for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
441− use helix_core::diagnostic::Severity;
442− if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) {
443− surface.set_stringn(
444− viewport.x,
445− viewport.y + i as u16,
446− "●",
447− 1,
448− match diagnostic.severity {
449− Some(Severity::Error) => error,
450− Some(Severity::Warning) | None => warning,
451− Some(Severity::Info) => info,
452− Some(Severity::Hint) => hint,
453− },
454− );
455− }
424+ fn diagnostic(
425+ doc: &Document,
426+ _view: &View,
427+ theme: &Theme,
428+ _config: &Config,
429+ _is_focused: bool,
430+ _width: usize,
431+ ) -> GutterFn {
432+ let warning = theme.get("warning");
433+ let error = theme.get("error");
434+ let info = theme.get("info");
435+ let hint = theme.get("hint");
436+ let diagnostics = doc.diagnostics().to_vec(); // TODO
437+
438+ Box::new(move |line: usize, _selected: bool| {
439+ use helix_core::diagnostic::Severity;
440+ if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) {
441+ return Some((
442+ "●".to_string(),
443+ match diagnostic.severity {
444+ Some(Severity::Error) => error,
445+ Some(Severity::Warning) | None => warning,
446+ Some(Severity::Info) => info,
447+ Some(Severity::Hint) => hint,
448+ },
449+ ));
450+ }
451+ None
452+ })
453+ }
456454
457− let selected = cursors.contains(&line);
455+ fn line_number(
456+ doc: &Document,
457+ view: &View,
458+ theme: &Theme,
459+ config: &Config,
460+ is_focused: bool,
461+ width: usize,
462+ ) -> GutterFn {
463+ let text = doc.text().slice(..);
464+ let last_line = view.last_line(doc);
465+ // Whether to draw the line number for the last line of the
466+ // document or not. We only draw it if it's not an empty line.
467+ let draw_last = text.line_to_byte(last_line) < text.len_bytes();
458468
459− let text = if line == last_line && !draw_last {
460− " ~".into()
461− } else {
462− let line = match config.line_number {
463− LineNumber::Absolute => line + 1,
464− LineNumber::Relative => {
465− if current_line == line {
466− line + 1
467− } else {
468− abs_diff(current_line, line)
469− }
470− }
471− };
472− format!("{:>5}", line)
473− };
474− surface.set_stringn(
475− viewport.x + 1,
476− viewport.y + i as u16,
477− text,
478− 5,
479− if selected && is_focused {
480− linenr_select
469+ let linenr = theme.get("ui.linenr");
470+ let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr);
471+
472+ let current_line = doc
473+ .text()
474+ .char_to_line(doc.selection(view.id).primary().cursor(text));
475+
476+ let config = config.line_number;
477+
478+ Box::new(move |line: usize, selected: bool| {
479+ if line == last_line && !draw_last {
480+ Some((format!("{:>1$}", '~', width), linenr))
481481 } else {
482− linenr
483− },
484− );
482+ let line = match config {
483+ LineNumber::Absolute => line + 1,
484+ LineNumber::Relative => {
485+ if current_line == line {
486+ line + 1
487+ } else {
488+ abs_diff(current_line, line)
489+ }
490+ }
491+ };
492+ let style = if selected && is_focused {
493+ linenr_select
494+ } else {
495+ linenr
496+ };
497+ Some((format!("{:>1$}", line, width), style))
498+ }
499+ })
500+ }
501+
502+ type GutterFn = Box<dyn Fn(usize, bool) -> Option<(String, Style)>>;
503+ type Gutter = fn(&Document, &View, &Theme, &Config, bool, usize) -> GutterFn;
504+ let gutters: &[(Gutter, usize)] = &[(diagnostic, 1), (line_number, 5)];
505+
506+ let mut offset = 0;
507+ for (constructor, width) in gutters {
508+ let gutter = constructor(doc, view, theme, config, is_focused, *width);
509+ for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
510+ let selected = cursors.contains(&line);
511+
512+ if let Some((text, style)) = gutter(line, selected) {
513+ surface.set_stringn(viewport.x + offset, viewport.y + i as u16, text, 5, style);
514+ }
515+ }
516+ offset += *width as u16;
485517 }
486518 }
487519