diff --git a/internal/config/settings.go b/internal/config/settings.go index 14e5f18b..38c3751c 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -70,7 +70,7 @@ var defaultCommonSettings = map[string]interface{}{ "hltrailingws": false, "ignorecase": true, "incsearch": true, - "indentchar": " ", + "indentchar": " ", // Deprecated "keepautoindent": false, "matchbrace": true, "matchbraceleft": true, @@ -88,6 +88,7 @@ var defaultCommonSettings = map[string]interface{}{ "scrollbar": false, "scrollmargin": float64(3), "scrollspeed": float64(2), + "showchars": "", "smartpaste": true, "softwrap": false, "splitbottom": true, @@ -210,6 +211,7 @@ func validateParsedSettings() error { } continue } + if _, ok := defaults[k]; ok { if e := verifySetting(k, v, defaults[k]); e != nil { err = e diff --git a/internal/display/bufwindow.go b/internal/display/bufwindow.go index 0abced39..e780cab0 100644 --- a/internal/display/bufwindow.go +++ b/internal/display/bufwindow.go @@ -2,6 +2,7 @@ package display import ( "strconv" + "strings" runewidth "github.com/mattn/go-runewidth" "github.com/micro-editor/tcell/v2" @@ -450,6 +451,30 @@ func (w *BufWindow) displayBuffer() { cursors := b.GetCursors() curStyle := config.DefStyle + + // Parse showchars which is in the format of key1=val1,key2=val2,... + spacechars := " " + tabchars := b.Settings["indentchar"].(string) + var indentspacechars string + var indenttabchars string + for _, entry := range strings.Split(b.Settings["showchars"].(string), ",") { + split := strings.SplitN(entry, "=", 2) + if len(split) < 2 { + continue + } + key, val := split[0], split[1] + switch key { + case "space": + spacechars = val + case "tab": + tabchars = val + case "ispace": + indentspacechars = val + case "itab": + indenttabchars = val + } + } + for ; vloc.Y < w.bufHeight; vloc.Y++ { vloc.X = 0 @@ -495,7 +520,7 @@ func (w *BufWindow) displayBuffer() { bloc.X = bslice // returns the rune to be drawn, style of it and if the bg should be preserved - getRuneStyle := func(r rune, style tcell.Style, isplaceholder bool) (rune, tcell.Style, bool) { + getRuneStyle := func(r rune, style tcell.Style, showoffset int, linex int, isplaceholder bool) (rune, tcell.Style, bool) { if nColsBeforeStart > 0 || vloc.Y < 0 || isplaceholder { return r, style, false } @@ -518,19 +543,33 @@ func (w *BufWindow) displayBuffer() { return r, style, false } - var drawrune rune - if r == '\t' { - indentrunes := []rune(b.Settings["indentchar"].(string)) - // if empty indentchar settings, use space - if len(indentrunes) == 0 { - indentrunes = []rune{' '} + var indentrunes []rune + switch r { + case '\t': + if bloc.X < leadingwsEnd && indenttabchars != "" { + indentrunes = []rune(indenttabchars) + } else { + indentrunes = []rune(tabchars) } + case ' ': + if linex%tabsize == 0 && bloc.X < leadingwsEnd && indentspacechars != "" { + indentrunes = []rune(indentspacechars) + } else { + indentrunes = []rune(spacechars) + } + } - drawrune = indentrunes[0] - if s, ok := config.Colorscheme["indent-char"]; ok { - fg, _, _ := s.Decompose() - style = style.Foreground(fg) - } + var drawrune rune + if showoffset < len(indentrunes) { + drawrune = indentrunes[showoffset] + } else { + // use space if no showchars or after we showed showchars + drawrune = ' ' + } + + if s, ok := config.Colorscheme["indent-char"]; ok { + fg, _, _ := s.Decompose() + style = style.Foreground(fg) } preservebg := false @@ -692,6 +731,7 @@ func (w *BufWindow) displayBuffer() { width := 0 + linex := totalwidth switch r { case '\t': ts := tabsize - (totalwidth % tabsize) @@ -732,15 +772,15 @@ func (w *BufWindow) displayBuffer() { } for _, r := range word { - drawrune, drawstyle, preservebg := getRuneStyle(r.r, r.style, false) + drawrune, drawstyle, preservebg := getRuneStyle(r.r, r.style, 0, linex, false) draw(drawrune, r.combc, drawstyle, true, true, preservebg) // Draw extra characters for tabs or wide runes for i := 1; i < r.width; i++ { if r.r == '\t' { - drawrune, drawstyle, preservebg = getRuneStyle('\t', r.style, false) + drawrune, drawstyle, preservebg = getRuneStyle('\t', r.style, i, linex+i, false) } else { - drawrune, drawstyle, preservebg = getRuneStyle(' ', r.style, true) + drawrune, drawstyle, preservebg = getRuneStyle(' ', r.style, i, linex+i, true) } draw(drawrune, nil, drawstyle, true, false, preservebg) } @@ -787,7 +827,7 @@ func (w *BufWindow) displayBuffer() { if vloc.X != maxWidth { // Display newline within a selection - drawrune, drawstyle, preservebg := getRuneStyle(' ', config.DefStyle, true) + drawrune, drawstyle, preservebg := getRuneStyle(' ', config.DefStyle, 0, totalwidth, true) draw(drawrune, nil, drawstyle, true, true, preservebg) } diff --git a/runtime/help/options.md b/runtime/help/options.md index e16d2259..4b21e018 100644 --- a/runtime/help/options.md +++ b/runtime/help/options.md @@ -203,12 +203,8 @@ Here are the available options: default value: `true` -* `indentchar`: sets the indentation character. This will not be inserted into - files; it is only a visual indicator that whitespace is present. If set to a - printing character, it functions as a subset of the "show invisibles" - setting available in many other text editors. The color of this character is - determined by the `indent-char` field in the current theme rather than the - default text color. +* `indentchar`: sets the character to be shown to display tab characters. + This option is **deprecated**, use the `tab` key in `showchars` option instead. default value: ` ` (space) @@ -386,6 +382,25 @@ Here are the available options: default value: `2` +* `showchars`: sets what characters to be shown to display various invisible + characters in the file. The characters shown will not be inserted into files. + This option is specified in the form of `key1=value1,key2=value2,...`. + + Here are the list of keys: + - `space`: space characters + - `tab`: tab characters. If set, overrides the `indentchar` option. + - `ispace`: space characters at indent position before the first visible + character in a line. If this is not set, `space` will be shown + instead. + - `itab`: tab characters before the first visible character in a line. + If this is not set, `tab` will be shown instead. + An example of this option value could be `tab=>,space=.,itab=|>,ispace=|` + + The color of the shown character is determined by the `indent-char` + field in the current theme rather than the default text color. + + default value: `` + * `smartpaste`: add leading whitespace when pasting multiple lines. This will attempt to preserve the current indentation level when pasting an unindented block. @@ -577,6 +592,7 @@ so that you can see what the formatting should look like. "scrollbarchar": "|", "scrollmargin": 3, "scrollspeed": 2, + "showchars": "", "smartpaste": true, "softwrap": false, "splitbottom": true,