@ -34,7 +34,7 @@ pub struct EditorView {
last_insert : ( commands ::Command , Vec < KeyEvent > ) ,
last_insert : ( commands ::Command , Vec < KeyEvent > ) ,
completion : Option < Completion > ,
completion : Option < Completion > ,
spinners : ProgressSpinners ,
spinners : ProgressSpinners ,
pub autoinfo : Option < Info > ,
autoinfo : Option < Info > ,
}
}
pub const GUTTER_OFFSET : u16 = 7 ; // 1 diagnostic + 5 linenr + 1 gutter
pub const GUTTER_OFFSET : u16 = 7 ; // 1 diagnostic + 5 linenr + 1 gutter
@ -78,8 +78,26 @@ impl EditorView {
view . area . width - GUTTER_OFFSET ,
view . area . width - GUTTER_OFFSET ,
view . area . height . saturating_sub ( 1 ) ,
view . area . height . saturating_sub ( 1 ) ,
) ; // - 1 for statusline
) ; // - 1 for statusline
let offset = Position ::new ( view . first_line , view . first_col ) ;
let height = view . area . height . saturating_sub ( 1 ) ; // - 1 for statusline
self . render_buffer ( doc , view , area , surface , theme , is_focused , loader ) ;
let highlights = Self ::doc_syntax_highlights ( doc , offset , height , theme , loader ) ;
let highlights = syntax ::merge ( highlights , Self ::doc_diagnostics_highlights ( doc , theme ) ) ;
let highlights : Box < dyn Iterator < Item = HighlightEvent > > = if is_focused {
Box ::new ( syntax ::merge (
highlights ,
Self ::doc_selection_highlights ( doc , view , theme ) ,
) )
} else {
Box ::new ( highlights )
} ;
Self ::render_text_highlights ( doc , offset , area , surface , theme , highlights ) ;
Self ::render_gutter ( doc , view , area , surface , theme ) ;
if is_focused {
Self ::render_focused_view_elements ( view , doc , area , theme , surface ) ;
}
// if we're not at the edge of the screen, draw a right border
// if we're not at the edge of the screen, draw a right border
if viewport . right ( ) ! = view . area . right ( ) {
if viewport . right ( ) ! = view . area . right ( ) {
@ -94,7 +112,7 @@ impl EditorView {
}
}
}
}
self . render_diagnostics ( doc , view , area , surface , theme , is_focused );
self . render_diagnostics ( doc , view , area , surface , theme );
let area = Rect ::new (
let area = Rect ::new (
view . area . x ,
view . area . x ,
@ -105,31 +123,34 @@ impl EditorView {
self . render_statusline ( doc , view , area , surface , theme , is_focused ) ;
self . render_statusline ( doc , view , area , surface , theme , is_focused ) ;
}
}
/// Get syntax highlights for a document in a view represented by the first line
/// and column (`offset`) and the last line. This is done instead of using a view
/// directly to enable rendering syntax highlighted docs anywhere (eg. picker preview)
#[ allow(clippy::too_many_arguments) ]
#[ allow(clippy::too_many_arguments) ]
pub fn render_buffer (
pub fn doc_syntax_highlights < ' doc > (
& self ,
doc : & ' doc Document ,
doc : & Document ,
offset : Position ,
view : & View ,
height : u16 ,
viewport : Rect ,
surface : & mut Surface ,
theme : & Theme ,
theme : & Theme ,
is_focused : bool ,
loader : & syntax ::Loader ,
loader : & syntax ::Loader ,
) {
) -> Box < dyn Iterator < Item = HighlightEvent > + ' doc > {
let text = doc . text ( ) . slice ( .. ) ;
let text = doc . text ( ) . slice ( .. ) ;
let last_line = std ::cmp ::min (
let last_line = view . last_line ( doc ) ;
// Saturating subs to make it inclusive zero indexing.
( offset . row + height as usize ) . saturating_sub ( 1 ) ,
doc . text ( ) . len_lines ( ) . saturating_sub ( 1 ) ,
) ;
let range = {
let range = {
// calculate viewport byte ranges
// calculate viewport byte ranges
let start = text . line_to_byte ( view. first_line ) ;
let start = text . line_to_byte ( offset. row ) ;
let end = text . line_to_byte ( last_line + 1 ) ;
let end = text . line_to_byte ( last_line + 1 ) ;
start .. end
start .. end
} ;
} ;
// TODO: range doesn't actually restrict source, just highlight range
// TODO: range doesn't actually restrict source, just highlight range
let highlights : Vec < _ > = match doc . syntax ( ) {
let highlights = match doc . syntax ( ) {
Some ( syntax ) = > {
Some ( syntax ) = > {
let scopes = theme . scopes ( ) ;
let scopes = theme . scopes ( ) ;
syntax
syntax
@ -151,20 +172,16 @@ impl EditorView {
Some ( config_ref )
Some ( config_ref )
} )
} )
} )
} )
. map ( | event | event . unwrap ( ) )
. collect ( ) // TODO: we collect here to avoid holding the lock, fix later
. collect ( ) // TODO: we collect here to avoid holding the lock, fix later
}
}
None = > vec! [ Ok ( HighlightEvent ::Source {
None = > vec! [ HighlightEvent ::Source {
start : range . start ,
start : range . start ,
end : range . end ,
end : range . end ,
} ) ] ,
} ] ,
} ;
}
let mut spans = Vec ::new ( ) ;
. into_iter ( )
let mut visual_x = 0 u16 ;
. map ( move | event | match event {
let mut line = 0 u16 ;
let tab_width = doc . tab_width ( ) ;
let tab = " " . repeat ( tab_width ) ;
let highlights = highlights . into_iter ( ) . map ( | event | match event . unwrap ( ) {
// convert byte offsets to char offset
// convert byte offsets to char offset
HighlightEvent ::Source { start , end } = > {
HighlightEvent ::Source { start , end } = > {
let start = ensure_grapheme_boundary_next ( text , text . byte_to_char ( start ) ) ;
let start = ensure_grapheme_boundary_next ( text , text . byte_to_char ( start ) ) ;
@ -174,13 +191,44 @@ impl EditorView {
event = > event ,
event = > event ,
} ) ;
} ) ;
let selections = doc . selection ( view . id ) ;
Box ::new ( highlights )
let primary_idx = selections . primary_index ( ) ;
}
/// Get highlight spans for document diagnostics
pub fn doc_diagnostics_highlights (
doc : & Document ,
theme : & Theme ,
) -> Vec < ( usize , std ::ops ::Range < usize > ) > {
let diagnostic_scope = theme
. find_scope_index ( "diagnostic" )
. or_else ( | | theme . find_scope_index ( "ui.cursor" ) )
. or_else ( | | theme . find_scope_index ( "ui.selection" ) )
. expect ( "no selection scope found!" ) ;
doc . diagnostics ( )
. iter ( )
. map ( | diagnostic | {
(
diagnostic_scope ,
diagnostic . range . start .. diagnostic . range . end ,
)
} )
. collect ( )
}
/// Get highlight spans for selections in a document view.
pub fn doc_selection_highlights (
doc : & Document ,
view : & View ,
theme : & Theme ,
) -> Vec < ( usize , std ::ops ::Range < usize > ) > {
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc . selection ( view . id ) ;
let primary_idx = selection . primary_index ( ) ;
let selection_scope = theme
let selection_scope = theme
. find_scope_index ( "ui.selection" )
. find_scope_index ( "ui.selection" )
. expect ( "no selection scope found!" ) ;
. expect ( "no selection scope found!" ) ;
let base_cursor_scope = theme
let base_cursor_scope = theme
. find_scope_index ( "ui.cursor" )
. find_scope_index ( "ui.cursor" )
. unwrap_or ( selection_scope ) ;
. unwrap_or ( selection_scope ) ;
@ -192,9 +240,6 @@ impl EditorView {
}
}
. unwrap_or ( base_cursor_scope ) ;
. unwrap_or ( base_cursor_scope ) ;
let highlights : Box < dyn Iterator < Item = HighlightEvent > > = if is_focused {
// TODO: primary + insert mode patching:
// (ui.cursor.primary).patch(mode).unwrap_or(cursor)
let primary_cursor_scope = theme
let primary_cursor_scope = theme
. find_scope_index ( "ui.cursor.primary" )
. find_scope_index ( "ui.cursor.primary" )
. unwrap_or ( cursor_scope ) ;
. unwrap_or ( cursor_scope ) ;
@ -202,9 +247,8 @@ impl EditorView {
. find_scope_index ( "ui.selection.primary" )
. find_scope_index ( "ui.selection.primary" )
. unwrap_or ( selection_scope ) ;
. unwrap_or ( selection_scope ) ;
// inject selections as highlight scopes
let mut spans : Vec < ( usize , std ::ops ::Range < usize > ) > = Vec ::new ( ) ;
let mut spans : Vec < ( usize , std ::ops ::Range < usize > ) > = Vec ::new ( ) ;
for ( i , range ) in selection s . iter ( ) . enumerate ( ) {
for ( i , range ) in selection . iter ( ) . enumerate ( ) {
let ( cursor_scope , selection_scope ) = if i = = primary_idx {
let ( cursor_scope , selection_scope ) = if i = = primary_idx {
( primary_cursor_scope , primary_selection_scope )
( primary_cursor_scope , primary_selection_scope )
} else {
} else {
@ -231,25 +275,24 @@ impl EditorView {
}
}
}
}
Box ::new ( syntax ::merge ( highlights , spans ) )
spans
} else {
}
Box ::new ( highlights )
} ;
// diagnostic injection
pub fn render_text_highlights < H : Iterator < Item = HighlightEvent > > (
let diagnostic_scope = theme . find_scope_index ( "diagnostic" ) . unwrap_or ( cursor_scope ) ;
doc : & Document ,
let highlights = Box ::new ( syntax ::merge (
offset : Position ,
highlights ,
viewport : Rect ,
doc . diagnostics ( )
surface : & mut Surface ,
. iter ( )
theme : & Theme ,
. map ( | diagnostic | {
highlights : H ,
(
) {
diagnostic_scope ,
let text = doc . text ( ) . slice ( .. ) ;
diagnostic . range . start .. diagnostic . range . end ,
)
let mut spans = Vec ::new ( ) ;
} )
let mut visual_x = 0 u16 ;
. collect ( ) ,
let mut line = 0 u16 ;
) ) ;
let tab_width = doc . tab_width ( ) ;
let tab = " " . repeat ( tab_width ) ;
' outer : for event in highlights {
' outer : for event in highlights {
match event {
match event {
@ -273,14 +316,14 @@ impl EditorView {
} ) ;
} ) ;
for grapheme in RopeGraphemes ::new ( text ) {
for grapheme in RopeGraphemes ::new ( text ) {
let out_of_bounds = visual_x < view. first_ col as u16
let out_of_bounds = visual_x < offset. col as u16
| | visual_x > = viewport . width + view. first_ col as u16 ;
| | visual_x > = viewport . width + offset. col as u16 ;
if LineEnding ::from_rope_slice ( & grapheme ) . is_some ( ) {
if LineEnding ::from_rope_slice ( & grapheme ) . is_some ( ) {
if ! out_of_bounds {
if ! out_of_bounds {
// we still want to render an empty cell with the style
// we still want to render an empty cell with the style
surface . set_string (
surface . set_string (
viewport . x + visual_x - view. first_ col as u16 ,
viewport . x + visual_x - offset. col as u16 ,
viewport . y + line ,
viewport . y + line ,
" " ,
" " ,
style ,
style ,
@ -310,7 +353,7 @@ impl EditorView {
if ! out_of_bounds {
if ! out_of_bounds {
// if we're offscreen just keep going until we hit a new line
// if we're offscreen just keep going until we hit a new line
surface . set_string (
surface . set_string (
viewport . x + visual_x - view. first_ col as u16 ,
viewport . x + visual_x - offset. col as u16 ,
viewport . y + line ,
viewport . y + line ,
grapheme ,
grapheme ,
style ,
style ,
@ -323,65 +366,33 @@ impl EditorView {
}
}
}
}
}
}
// render gutters
let linenr : Style = theme . get ( "ui.linenr" ) ;
let warning : Style = theme . get ( "warning" ) ;
let error : Style = theme . get ( "error" ) ;
let info : Style = theme . get ( "info" ) ;
let hint : Style = theme . get ( "hint" ) ;
// Whether to draw the line number for the last line of the
// document or not. We only draw it if it's not an empty line.
let draw_last = text . line_to_byte ( last_line ) < text . len_bytes ( ) ;
for ( i , line ) in ( view . first_line .. ( last_line + 1 ) ) . enumerate ( ) {
use helix_core ::diagnostic ::Severity ;
if let Some ( diagnostic ) = doc . diagnostics ( ) . iter ( ) . find ( | d | d . line = = line ) {
surface . set_stringn (
viewport . x - GUTTER_OFFSET ,
viewport . y + i as u16 ,
"●" ,
1 ,
match diagnostic . severity {
Some ( Severity ::Error ) = > error ,
Some ( Severity ::Warning ) | None = > warning ,
Some ( Severity ::Info ) = > info ,
Some ( Severity ::Hint ) = > hint ,
} ,
) ;
}
// Line numbers having selections are rendered
// differently, further below.
let line_number_text = if line = = last_line & & ! draw_last {
" ~" . into ( )
} else {
format! ( "{:>5}" , line + 1 )
} ;
surface . set_stringn (
viewport . x + 1 - GUTTER_OFFSET ,
viewport . y + i as u16 ,
line_number_text ,
5 ,
linenr ,
) ;
}
}
// render selections and selected linenr(s)
/// Render brace match, selected line numbers, etc (meant for the focused view only)
let linenr_select : Style = theme
pub fn render_focused_view_elements (
. try_get ( "ui.linenr.selected" )
view : & View ,
. unwrap_or_else ( | | theme . get ( "ui.linenr" ) ) ;
doc : & Document ,
viewport : Rect ,
if is_focused {
theme : & Theme ,
surface : & mut Surface ,
) {
let text = doc . text ( ) . slice ( .. ) ;
let selection = doc . selection ( view . id ) ;
let last_line = view . last_line ( doc ) ;
let screen = {
let screen = {
let start = text . line_to_char ( view . first_line ) ;
let start = text . line_to_char ( view . first_line ) ;
let end = text . line_to_char ( last_line + 1 ) + 1 ; // +1 for cursor at end of text.
let end = text . line_to_char ( last_line + 1 ) + 1 ; // +1 for cursor at end of text.
Range ::new ( start , end )
Range ::new ( start , end )
} ;
} ;
let selection = doc . selection ( view . id ) ;
// render selected linenr(s)
let linenr_select : Style = theme
. try_get ( "ui.linenr.selected" )
. unwrap_or_else ( | | theme . get ( "ui.linenr" ) ) ;
// Whether to draw the line number for the last line of the
// document or not. We only draw it if it's not an empty line.
let draw_last = text . line_to_byte ( last_line ) < text . len_bytes ( ) ;
for selection in selection . iter ( ) . filter ( | range | range . overlaps ( & screen ) ) {
for selection in selection . iter ( ) . filter ( | range | range . overlaps ( & screen ) ) {
let head = view . screen_coords_at_pos (
let head = view . screen_coords_at_pos (
@ -394,7 +405,7 @@ impl EditorView {
} ,
} ,
) ;
) ;
if let Some ( head ) = head {
if let Some ( head ) = head {
// Draw line number for selected lines.
// Highlight line number for selected lines.
let line_number = view . first_line + head . row ;
let line_number = view . first_line + head . row ;
let line_number_text = if line_number = = last_line & & ! draw_last {
let line_number_text = if line_number = = last_line & & ! draw_last {
" ~" . into ( )
" ~" . into ( )
@ -402,13 +413,14 @@ impl EditorView {
format! ( "{:>5}" , line_number + 1 )
format! ( "{:>5}" , line_number + 1 )
} ;
} ;
surface . set_stringn (
surface . set_stringn (
viewport . x + 1 - GUTTER_OFFSET ,
viewport . x - GUTTER_OFFSET + 1 ,
viewport . y + head . row as u16 ,
viewport . y + head . row as u16 ,
line_number_text ,
line_number_text ,
5 ,
5 ,
linenr_select ,
linenr_select ,
) ;
) ;
// Highlight matching braces
// TODO: set cursor position for IME
// TODO: set cursor position for IME
if let Some ( syntax ) = doc . syntax ( ) {
if let Some ( syntax ) = doc . syntax ( ) {
use helix_core ::match_brackets ;
use helix_core ::match_brackets ;
@ -431,10 +443,7 @@ impl EditorView {
} ) ;
} ) ;
surface
surface
. get_mut (
. get_mut ( viewport . x + pos . col as u16 , viewport . y + pos . row as u16 )
viewport . x + pos . col as u16 ,
viewport . y + pos . row as u16 ,
)
. set_style ( style ) ;
. set_style ( style ) ;
}
}
}
}
@ -442,6 +451,60 @@ impl EditorView {
}
}
}
}
}
}
#[ allow(clippy::too_many_arguments) ]
pub fn render_gutter (
doc : & Document ,
view : & View ,
viewport : Rect ,
surface : & mut Surface ,
theme : & Theme ,
) {
let text = doc . text ( ) . slice ( .. ) ;
let last_line = view . last_line ( doc ) ;
let linenr = theme . get ( "ui.linenr" ) ;
let warning = theme . get ( "warning" ) ;
let error = theme . get ( "error" ) ;
let info = theme . get ( "info" ) ;
let hint = theme . get ( "hint" ) ;
// Whether to draw the line number for the last line of the
// document or not. We only draw it if it's not an empty line.
let draw_last = text . line_to_byte ( last_line ) < text . len_bytes ( ) ;
for ( i , line ) in ( view . first_line .. ( last_line + 1 ) ) . enumerate ( ) {
use helix_core ::diagnostic ::Severity ;
if let Some ( diagnostic ) = doc . diagnostics ( ) . iter ( ) . find ( | d | d . line = = line ) {
surface . set_stringn (
viewport . x - GUTTER_OFFSET ,
viewport . y + i as u16 ,
"●" ,
1 ,
match diagnostic . severity {
Some ( Severity ::Error ) = > error ,
Some ( Severity ::Warning ) | None = > warning ,
Some ( Severity ::Info ) = > info ,
Some ( Severity ::Hint ) = > hint ,
} ,
) ;
}
// Line numbers having selections are rendered
// differently, further below.
let line_number_text = if line = = last_line & & ! draw_last {
" ~" . into ( )
} else {
format! ( "{:>5}" , line + 1 )
} ;
surface . set_stringn (
viewport . x + 1 - GUTTER_OFFSET ,
viewport . y + i as u16 ,
line_number_text ,
5 ,
linenr ,
) ;
}
}
}
pub fn render_diagnostics (
pub fn render_diagnostics (
@ -451,7 +514,6 @@ impl EditorView {
viewport : Rect ,
viewport : Rect ,
surface : & mut Surface ,
surface : & mut Surface ,
theme : & Theme ,
theme : & Theme ,
_is_focused : bool ,
) {
) {
use helix_core ::diagnostic ::Severity ;
use helix_core ::diagnostic ::Severity ;
use tui ::{
use tui ::{
@ -469,10 +531,10 @@ impl EditorView {
diagnostic . range . start < = cursor & & diagnostic . range . end > = cursor
diagnostic . range . start < = cursor & & diagnostic . range . end > = cursor
} ) ;
} ) ;
let warning : Style = theme . get ( "warning" ) ;
let warning = theme . get ( "warning" ) ;
let error : Style = theme . get ( "error" ) ;
let error = theme . get ( "error" ) ;
let info : Style = theme . get ( "info" ) ;
let info = theme . get ( "info" ) ;
let hint : Style = theme . get ( "hint" ) ;
let hint = theme . get ( "hint" ) ;
// Vec::with_capacity(diagnostics.len()); // rough estimate
// Vec::with_capacity(diagnostics.len()); // rough estimate
let mut lines = Vec ::new ( ) ;
let mut lines = Vec ::new ( ) ;
@ -961,7 +1023,7 @@ impl Component for EditorView {
}
}
}
}
fn render ( & self , area : Rect , surface : & mut Surface , cx : & mut Context ) {
fn render ( & mut self , area : Rect , surface : & mut Surface , cx : & mut Context ) {
// clear with background color
// clear with background color
surface . set_style ( area , cx . editor . theme . get ( "ui.background" ) ) ;
surface . set_style ( area , cx . editor . theme . get ( "ui.background" ) ) ;
@ -983,7 +1045,7 @@ impl Component for EditorView {
) ;
) ;
}
}
if let Some ( ref info ) = self . autoinfo {
if let Some ( ref mut info ) = self . autoinfo {
info . render ( area , surface , cx ) ;
info . render ( area , surface , cx ) ;
}
}
@ -1030,7 +1092,7 @@ impl Component for EditorView {
) ;
) ;
}
}
if let Some ( completion ) = & self . completion {
if let Some ( completion ) = self . completion . as_mut ( ) {
completion . render ( area , surface , cx ) ;
completion . render ( area , surface , cx ) ;
}
}
}
}