Skip to content

Commit

Permalink
editor grid cells now hold runes instead of strings
Browse files Browse the repository at this point in the history
  • Loading branch information
Yatao Li committed Oct 24, 2020
1 parent 080c448 commit 13e3a6e
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 60 deletions.
2 changes: 1 addition & 1 deletion ViewModels/CursorViewModel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type CursorViewModel(cursorMode: int option) =
member val typeface: string = "" with get,set
member val wtypeface: string = "" with get,set
member val fontSize: float = 8.0 with get,set
member val text: string = "" with get,set
member val text: Rune = Rune.empty with get,set
member val fg: Color = Colors.White with get,set
member val bg: Color = Colors.Black with get,set
member val sp: Color = Colors.Red with get,set
Expand Down
2 changes: 1 addition & 1 deletion ViewModels/EditorViewModel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ type EditorViewModel(_gridid: int, ?parent: EditorViewModel, ?_gridsize: GridSiz
let fontConfig() =
// It turns out the space " " advances farest...
// So we measure it as the width.
let s, w, h = MeasureText(" ", theme.guifont, theme.guifontwide, theme.fontsize, m_gridscale)
let s, w, h = MeasureText(Rune.empty, theme.guifont, theme.guifontwide, theme.fontsize, m_gridscale)
m_glyphsize <- Size(w, h)
m_fontsize <- s
trace _gridid "fontConfig: glyphsize=%A, measured font size=%A" m_glyphsize m_fontsize
Expand Down
2 changes: 1 addition & 1 deletion Views/Cursor.xaml.fs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ type Cursor() as this =
let bounds = Rect(this.Bounds.Size)
let render_block (ctx: 'a) =
if this.IsActive then
RenderText(ctx, bounds, scale, fgpaint, bgpaint, sppaint, this.ViewModel.underline, this.ViewModel.undercurl, this.ViewModel.text, ValueNone)
RenderText(ctx, bounds, scale, fgpaint, bgpaint, sppaint, this.ViewModel.underline, this.ViewModel.undercurl, this.ViewModel.text.ToString(), ValueNone)
else
let brush = SolidColorBrush(this.ViewModel.bg)
ctx.DrawRectangle(brush, Pen(brush), RoundedRect(bounds))
Expand Down
47 changes: 22 additions & 25 deletions Views/Editor.xaml.fs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ module private EditorHelper =
if vm <> Unchecked.defaultof<_> then (vm :> IGridUI).Id.ToString() else "(no vm attached)"
FVim.log.trace ("editor #" + nr) fmt


open EditorHelper
open System.Text

type Editor() as this =
inherit Canvas()
Expand Down Expand Up @@ -84,16 +84,11 @@ type Editor() as this =
let mutable bgpaint = null
let mutable sppaint = null

let drawBuffer (ctx: IDrawingContextImpl) row col colend hlid (str: string list) (issym: bool) =

let x =
match str with
| x :: _ -> x
| _ -> " "
let drawBuffer (ctx: IDrawingContextImpl) row col colend hlid (issym: bool) =

let font, fontwide, fontsize = grid_vm.GetFontAttrs()
let fg, bg, sp, attrs = theme.GetDrawAttrs hlid
let shaper, typeface = GetTypeface(x, attrs.italic, attrs.bold, font, fontwide)
let shaper, typeface = GetTypeface(grid_vm.[row, col].text, attrs.italic, attrs.bold, font, fontwide)

if fgpaint = null then
fgpaint <- new SKPaint()
Expand All @@ -115,7 +110,13 @@ type Editor() as this =
bgpaint.Color <- bg.ToSKColor()
sppaint.Color <- sp.ToSKColor()

let txt = String.Concat str
let txt =
let sb = StringBuilder()
for i = col to colend - 1 do
match grid_vm.[row, i] with
| { text = { c1 = c1; c2 = c2; isSurrogatePair = true } } -> sb.Append(c1).Append(c2) |> ignore
| { text = { c1 = c1 } } -> sb.Append(c1) |> ignore
sb.ToString()

let shaping =
if txt.Length > 1 && txt.Length < 5 && issym && States.font_ligature
Expand All @@ -130,43 +131,39 @@ type Editor() as this =
let drawBufferLine (ctx: IDrawingContextImpl) y x0 xN =
let xN = min xN grid_vm.Cols
let x0 = max x0 0
let y = (min y (grid_vm.Rows - 1)) |> max 0
let y = Math.Clamp(y, 0, (grid_vm.Rows - 1))
let mutable x': int = xN - 1
let mutable prev: GridBufferCell ref = ref grid_vm.[y, x']
let mutable str: string list = []
let mutable wc: CharType = wswidth (!prev).text
let mutable sym: bool = isProgrammingSymbol (!prev).text
let mutable wc: CharType = wswidth grid_vm.[y, x'].text
let mutable sym: bool = isProgrammingSymbol grid_vm.[y, x'].text
let mutable prev_hlid = grid_vm.[y, x'].hlid

let mutable bold =
let _, _, _, hl_attrs = theme.GetDrawAttrs (!prev).hlid
let _, _, _, hl_attrs = theme.GetDrawAttrs prev_hlid
hl_attrs.bold
// in each line we do backward rendering.
// the benefit is that the italic fonts won't be covered by later drawings
for x = xN - 1 downto x0 do
let current = ref grid_vm.[y, x]
let mytext = (!current).text
let current = grid_vm.[y, x]
let mytext = current.text
// !NOTE text shaping is slow. We only use shaping for
// a symbol-only span (for ligature drawing).
let mysym = isProgrammingSymbol mytext
let mywc = wswidth mytext
// !NOTE bold glyphs are generally wider than normal.
// Therefore, we have to break them into single glyphs
// to prevent overflow into later cells.
let prev_hlid = (!prev).hlid
let hlidchange = prev_hlid <> (!current).hlid
let hlidchange = prev_hlid <> current.hlid
if hlidchange || mywc <> wc || bold || sym <> mysym then
drawBuffer ctx y (x + 1) (x' + 1) prev_hlid str sym
drawBuffer ctx y (x + 1) (x' + 1) prev_hlid sym
wc <- mywc
sym <- mysym
x' <- x
str <- []
if hlidchange then
prev <- current
prev_hlid <- current.hlid
bold <-
let _, _, _, hl_attrs = theme.GetDrawAttrs (!current).hlid
let _, _, _, hl_attrs = theme.GetDrawAttrs prev_hlid
hl_attrs.bold
str <- mytext :: str
drawBuffer ctx y x0 (x' + 1) (!prev).hlid str sym
drawBuffer ctx y x0 (x' + 1) prev_hlid sym

let doWithDataContext fn =
match this.DataContext with
Expand Down
26 changes: 22 additions & 4 deletions def.fs
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,21 @@ type RgbAttr =
undercurl = false
}

[<Struct>]
type Rune =
{
c1: char
c2: char
isSurrogatePair: bool
}
with
override x.ToString() = if x.isSurrogatePair then sprintf "%c%c" x.c1 x.c2 else x.c1.ToString()
static member empty = { c1 = ' '; c2 = char 0; isSurrogatePair = false }

[<Struct>]
type GridCell =
{
text: string
text: Rune
hl_id: int option
repeat: int option
}
Expand Down Expand Up @@ -486,13 +497,20 @@ let parse_hi_attr (x: obj) =
-> Some {id = id; rgb_attr = rgb; cterm_attr = cterm; info = info }
| _ -> None

let (|Rune|_|) (x:obj) =
match x with
| :? string as x when x.Length = 0 -> Some Rune.empty
| :? string as x when x.Length = 1 -> Some { c1 = x.[0]; c2 = char 0; isSurrogatePair = false }
| :? string as x when x.Length = 2 -> Some { c1 = x.[0]; c2 = x.[1]; isSurrogatePair = true }
| _ -> failwithf "Rune parse failure: %O" x

let parse_grid_cell (x: obj) =
match x with
| ObjArray [| (String txt) |]
| ObjArray [| (Rune txt) |]
-> Some { text = txt; hl_id = None; repeat = None}
| ObjArray [| (String txt); (Integer32 hl_id) |]
| ObjArray [| (Rune txt); (Integer32 hl_id) |]
-> Some { text = txt; hl_id = Some hl_id; repeat = None}
| ObjArray [| (String txt); (Integer32 hl_id); (Integer32 repeat) |]
| ObjArray [| (Rune txt); (Integer32 hl_id); (Integer32 repeat) |]
-> Some { text = txt; hl_id = Some hl_id; repeat = Some repeat}
| _ -> None

Expand Down
9 changes: 5 additions & 4 deletions ui.fs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ type InputEvent =
[<Struct>]
type GridBufferCell =
{
mutable text: string
mutable text: Rune
mutable hlid: int32
}
with static member empty = { text = " "; hlid = 0 }
with static member empty = { text = Rune.empty; hlid = 0 }

[<Struct>]
type GridSize =
Expand Down Expand Up @@ -201,9 +201,9 @@ let GetTypeface(txt, italic, bold, font, wfont) =
| CharType.Emoji -> (emoji_shaper, emoji_typeface)
| _ -> _get font

let MeasureText (str: string, font: string, wfont: string, fontSize: float, scaling: float) =
let MeasureText (rune: Rune, font: string, wfont: string, fontSize: float, scaling: float) =
use paint = new SKPaint()
paint.Typeface <- snd <| GetTypeface(str, false, false, font, wfont)
paint.Typeface <- snd <| GetTypeface(rune, false, false, font, wfont)
paint.TextSize <- single fontSize
paint.IsAntialias <- States.font_antialias
paint.IsAutohinted <- States.font_autohint
Expand All @@ -218,6 +218,7 @@ let MeasureText (str: string, font: string, wfont: string, fontSize: float, scal
let mutable s = fontSize
let mutable w = 0.0
let mutable h = 0.0
let str = rune.ToString()

let search (sizeStep: int) =
let s' = fontSize + float(sizeStep) * 0.01
Expand Down
37 changes: 13 additions & 24 deletions wcwidth.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module FVim.wcwidth

open log
open System.Numerics
open def

// From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py
// at commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02):
Expand Down Expand Up @@ -563,35 +564,23 @@ let wcwidth(ucs: int) =
(*trace "wcwidth" "unknown codepoint: %c (%X)" (char ucs) (ucs)*)
CharType.Narrow

let wswidth(str: string) =
if System.String.IsNullOrEmpty str then CharType.Invisible
// prepend a low surrogate
else (string(char 0xDC00) + str)
|> Seq.map int
|> Seq.pairwise
|> Seq.choose (fun (a,b) ->
if System.Char.IsSurrogatePair(char a, char b)
then Some(0x10000 + (a - 0xD800) * 0x400 + (b - 0xDC00))
elif not <| System.Char.IsSurrogate(char b)
then Some(b)
else None)
|> Seq.map (wcwidth >> int)
|> Seq.max
|> LanguagePrimitives.EnumOfValue
let wswidth =
function
| { c1 = c1; c2 = c2; isSurrogatePair = true } -> wcwidth (0x10000 + (int c1 - 0xD800) * 0x400 + (int c2 - 0xDC00))
| { c1 = c1 } when System.Char.IsWhiteSpace c1 -> CharType.Invisible
| { c1 = c1 } -> wcwidth <| int c1

/// <summary>
/// true if the string could be a part of a programming
/// symbol ligature.
/// </summary>
let isProgrammingSymbol(str: string) =
if System.String.IsNullOrWhiteSpace str then false
else
let ch = str.[0]
match ch with
// disable the frequent symbols that's too expensive to draw
| '\'' | '"' | '{' | '}'
-> false
| _ -> System.Char.IsSymbol(ch) || System.Char.IsPunctuation(ch)
let isProgrammingSymbol =
function
| { isSurrogatePair = true } -> false
| { c1 = c1 } when System.Char.IsWhiteSpace c1 -> false
// disable the frequent symbols that's too expensive to draw
| { c1 = '\'' | '"' | '{' | '}' } -> false
| { c1 = chr } -> System.Char.IsSymbol chr || System.Char.IsPunctuation chr

let CharTypeWidth(x: CharType): int =
match x with
Expand Down

0 comments on commit 13e3a6e

Please sign in to comment.