@ -34,7 +34,7 @@ pub struct EditorView {
last_insert : ( commands ::Command , Vec < KeyEvent > ) ,
completion : Option < Completion > ,
spinners : ProgressSpinners ,
pub autoinfo : Option < Info > ,
autoinfo : Option < Info > ,
}
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 . height . saturating_sub ( 1 ) ,
) ; // - 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 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 (
view . area . x ,
@ -105,31 +123,34 @@ impl EditorView {
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) ]
pub fn render_buffer (
& self ,
doc : & Document ,
view : & View ,
viewport : Rect ,
surface : & mut Surface ,
pub fn doc_syntax_highlights < ' doc > (
doc : & ' doc Document ,
offset : Position ,
height : u16 ,
theme : & Theme ,
is_focused : bool ,
loader : & syntax ::Loader ,
) {
) -> Box < dyn Iterator < Item = HighlightEvent > + ' doc > {
let text = doc . text ( ) . slice ( .. ) ;
let last_line = view . last_line ( doc ) ;
let last_line = std ::cmp ::min (
// 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 = {
// 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 ) ;
start .. end
} ;
// TODO: range doesn't actually restrict source, just highlight range
let highlights : Vec < _ > = match doc . syntax ( ) {
let highlights = match doc . syntax ( ) {
Some ( syntax ) = > {
let scopes = theme . scopes ( ) ;
syntax
@ -151,20 +172,16 @@ impl EditorView {
Some ( config_ref )
} )
} )
. map ( | event | event . unwrap ( ) )
. collect ( ) // TODO: we collect here to avoid holding the lock, fix later
}
None = > vec! [ Ok ( HighlightEvent ::Source {
None = > vec! [ HighlightEvent ::Source {
start : range . start ,
end : range . end ,
} ) ] ,
} ;
let mut spans = Vec ::new ( ) ;
let mut visual_x = 0 u16 ;
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 ( ) {
} ] ,
}
. into_iter ( )
. map ( move | event | match event {
// convert byte offsets to char offset
HighlightEvent ::Source { start , end } = > {
let start = ensure_grapheme_boundary_next ( text , text . byte_to_char ( start ) ) ;
@ -174,13 +191,44 @@ impl EditorView {
event = > event ,
} ) ;
let selections = doc . selection ( view . id ) ;
let primary_idx = selections . primary_index ( ) ;
Box ::new ( highlights )
}
/// 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
. find_scope_index ( "ui.selection" )
. expect ( "no selection scope found!" ) ;
let base_cursor_scope = theme
. find_scope_index ( "ui.cursor" )
. unwrap_or ( selection_scope ) ;
@ -192,64 +240,59 @@ impl EditorView {
}
. 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
. find_scope_index ( "ui.cursor.primary" )
. unwrap_or ( cursor_scope ) ;
let primary_selection_scope = theme
. find_scope_index ( "ui.selection.primary" )
. unwrap_or ( selection_scope ) ;
// inject selections as highlight scopes
let mut spans : Vec < ( usize , std ::ops ::Range < usize > ) > = Vec ::new ( ) ;
for ( i , range ) in selections . iter ( ) . enumerate ( ) {
let ( cursor_scope , selection_scope ) = if i = = primary_idx {
( primary_cursor_scope , primary_selection_scope )
} else {
( cursor_scope , selection_scope )
} ;
let primary_cursor_scope = theme
. find_scope_index ( "ui.cursor.primary" )
. unwrap_or ( cursor_scope ) ;
let primary_selection_scope = theme
. find_scope_index ( "ui.selection.primary" )
. unwrap_or ( selection_scope ) ;
// Special-case: cursor at end of the rope.
if range . head = = range . anchor & & range . head = = text . len_chars ( ) {
spans . push ( ( cursor_scope , range . head .. range . head + 1 ) ) ;
continue ;
}
let mut spans : Vec < ( usize , std ::ops ::Range < usize > ) > = Vec ::new ( ) ;
for ( i , range ) in selection . iter ( ) . enumerate ( ) {
let ( cursor_scope , selection_scope ) = if i = = primary_idx {
( primary_cursor_scope , primary_selection_scope )
} else {
( cursor_scope , selection_scope )
} ;
let range = range . min_width_1 ( text ) ;
if range . head > range . anchor {
// Standard case.
let cursor_start = prev_grapheme_boundary ( text , range . head ) ;
spans . push ( ( selection_scope , range . anchor .. cursor_start ) ) ;
spans . push ( ( cursor_scope , cursor_start .. range . head ) ) ;
} else {
// Reverse case.
let cursor_end = next_grapheme_boundary ( text , range . head ) ;
spans . push ( ( cursor_scope , range . head .. cursor_end ) ) ;
spans . push ( ( selection_scope , cursor_end .. range . anchor ) ) ;
}
// Special-case: cursor at end of the rope.
if range . head = = range . anchor & & range . head = = text . len_chars ( ) {
spans . push ( ( cursor_scope , range . head .. range . head + 1 ) ) ;
continue ;
}
Box ::new ( syntax ::merge ( highlights , spans ) )
} else {
Box ::new ( highlights )
} ;
let range = range . min_width_1 ( text ) ;
if range . head > range . anchor {
// Standard case.
let cursor_start = prev_grapheme_boundary ( text , range . head ) ;
spans . push ( ( selection_scope , range . anchor .. cursor_start ) ) ;
spans . push ( ( cursor_scope , cursor_start .. range . head ) ) ;
} else {
// Reverse case.
let cursor_end = next_grapheme_boundary ( text , range . head ) ;
spans . push ( ( cursor_scope , range . head .. cursor_end ) ) ;
spans . push ( ( selection_scope , cursor_end .. range . anchor ) ) ;
}
}
spans
}
pub fn render_text_highlights < H : Iterator < Item = HighlightEvent > > (
doc : & Document ,
offset : Position ,
viewport : Rect ,
surface : & mut Surface ,
theme : & Theme ,
highlights : H ,
) {
let text = doc . text ( ) . slice ( .. ) ;
// diagnostic injection
let diagnostic_scope = theme . find_scope_index ( "diagnostic" ) . unwrap_or ( cursor_scope ) ;
let highlights = Box ::new ( syntax ::merge (
highlights ,
doc . diagnostics ( )
. iter ( )
. map ( | diagnostic | {
(
diagnostic_scope ,
diagnostic . range . start .. diagnostic . range . end ,
)
} )
. collect ( ) ,
) ) ;
let mut spans = Vec ::new ( ) ;
let mut visual_x = 0 u16 ;
let mut line = 0 u16 ;
let tab_width = doc . tab_width ( ) ;
let tab = " " . repeat ( tab_width ) ;
' outer : for event in highlights {
match event {
@ -273,14 +316,14 @@ impl EditorView {
} ) ;
for grapheme in RopeGraphemes ::new ( text ) {
let out_of_bounds = visual_x < view. first_ col as u16
| | visual_x > = viewport . width + view. first_ col as u16 ;
let out_of_bounds = visual_x < offset. col as u16
| | visual_x > = viewport . width + offset. col as u16 ;
if LineEnding ::from_rope_slice ( & grapheme ) . is_some ( ) {
if ! out_of_bounds {
// we still want to render an empty cell with the style
surface . set_string (
viewport . x + visual_x - view. first_ col as u16 ,
viewport . x + visual_x - offset. col as u16 ,
viewport . y + line ,
" " ,
style ,
@ -310,7 +353,7 @@ impl EditorView {
if ! out_of_bounds {
// if we're offscreen just keep going until we hit a new line
surface . set_string (
viewport . x + visual_x - view. first_ col as u16 ,
viewport . x + visual_x - offset. col as u16 ,
viewport . y + line ,
grapheme ,
style ,
@ -323,14 +366,108 @@ impl EditorView {
}
}
}
}
/// Render brace match, selected line numbers, etc (meant for the focused view only)
pub fn render_focused_view_elements (
view : & View ,
doc : & Document ,
viewport : Rect ,
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 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.
Range ::new ( start , end )
} ;
// 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 ) ) {
let head = view . screen_coords_at_pos (
doc ,
text ,
if selection . head > selection . anchor {
selection . head - 1
} else {
selection . head
} ,
) ;
if let Some ( head ) = head {
// Highlight line number for selected lines.
let line_number = view . first_line + head . row ;
let line_number_text = if line_number = = last_line & & ! draw_last {
" ~" . into ( )
} else {
format! ( "{:>5}" , line_number + 1 )
} ;
surface . set_stringn (
viewport . x - GUTTER_OFFSET + 1 ,
viewport . y + head . row as u16 ,
line_number_text ,
5 ,
linenr_select ,
) ;
// Highlight matching braces
// TODO: set cursor position for IME
if let Some ( syntax ) = doc . syntax ( ) {
use helix_core ::match_brackets ;
let pos = doc
. selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ;
let pos = match_brackets ::find ( syntax , doc . text ( ) , pos )
. and_then ( | pos | view . screen_coords_at_pos ( doc , text , pos ) ) ;
if let Some ( pos ) = pos {
// ensure col is on screen
if ( pos . col as u16 ) < viewport . width + view . first_col as u16
& & pos . col > = view . first_col
{
let style = theme . try_get ( "ui.cursor.match" ) . unwrap_or_else ( | | {
Style ::default ( )
. add_modifier ( Modifier ::REVERSED )
. add_modifier ( Modifier ::DIM )
} ) ;
surface
. get_mut ( viewport . x + pos . col as u16 , viewport . y + pos . row as u16 )
. set_style ( style ) ;
}
}
}
}
}
}
// render gutters
#[ 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 : 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" ) ;
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.
@ -368,80 +505,6 @@ impl EditorView {
linenr ,
) ;
}
// render selections and selected linenr(s)
let linenr_select : Style = theme
. try_get ( "ui.linenr.selected" )
. unwrap_or_else ( | | theme . get ( "ui.linenr" ) ) ;
if is_focused {
let screen = {
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.
Range ::new ( start , end )
} ;
let selection = doc . selection ( view . id ) ;
for selection in selection . iter ( ) . filter ( | range | range . overlaps ( & screen ) ) {
let head = view . screen_coords_at_pos (
doc ,
text ,
if selection . head > selection . anchor {
selection . head - 1
} else {
selection . head
} ,
) ;
if let Some ( head ) = head {
// Draw line number for selected lines.
let line_number = view . first_line + head . row ;
let line_number_text = if line_number = = last_line & & ! draw_last {
" ~" . into ( )
} else {
format! ( "{:>5}" , line_number + 1 )
} ;
surface . set_stringn (
viewport . x + 1 - GUTTER_OFFSET ,
viewport . y + head . row as u16 ,
line_number_text ,
5 ,
linenr_select ,
) ;
// TODO: set cursor position for IME
if let Some ( syntax ) = doc . syntax ( ) {
use helix_core ::match_brackets ;
let pos = doc
. selection ( view . id )
. primary ( )
. cursor ( doc . text ( ) . slice ( .. ) ) ;
let pos = match_brackets ::find ( syntax , doc . text ( ) , pos )
. and_then ( | pos | view . screen_coords_at_pos ( doc , text , pos ) ) ;
if let Some ( pos ) = pos {
// ensure col is on screen
if ( pos . col as u16 ) < viewport . width + view . first_col as u16
& & pos . col > = view . first_col
{
let style = theme . try_get ( "ui.cursor.match" ) . unwrap_or_else ( | | {
Style ::default ( )
. add_modifier ( Modifier ::REVERSED )
. add_modifier ( Modifier ::DIM )
} ) ;
surface
. get_mut (
viewport . x + pos . col as u16 ,
viewport . y + pos . row as u16 ,
)
. set_style ( style ) ;
}
}
}
}
}
}
}
pub fn render_diagnostics (
@ -451,7 +514,6 @@ impl EditorView {
viewport : Rect ,
surface : & mut Surface ,
theme : & Theme ,
_is_focused : bool ,
) {
use helix_core ::diagnostic ::Severity ;
use tui ::{
@ -469,10 +531,10 @@ impl EditorView {
diagnostic . range . start < = cursor & & diagnostic . range . end > = cursor
} ) ;
let warning : Style = theme . get ( "warning" ) ;
let error : Style = theme . get ( "error" ) ;
let info : Style = theme . get ( "info" ) ;
let hint : Style = theme . get ( "hint" ) ;
let warning = theme . get ( "warning" ) ;
let error = theme . get ( "error" ) ;
let info = theme . get ( "info" ) ;
let hint = theme . get ( "hint" ) ;
// Vec::with_capacity(diagnostics.len()); // rough estimate
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
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 ) ;
}
@ -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 ) ;
}
}