]> git.lizzy.rs Git - micro.git/commitdiff
Implement word wrapping
authorDmitry Maluka <dmitrymaluka@gmail.com>
Wed, 17 Mar 2021 21:34:30 +0000 (22:34 +0100)
committerDmitry Maluka <dmitrymaluka@gmail.com>
Thu, 8 Apr 2021 21:54:10 +0000 (23:54 +0200)
Fixes #264
Fixes #1644

internal/config/settings.go
internal/display/bufwindow.go
internal/display/softwrap.go
runtime/help/options.md

index 7ccd3516580f48d65643d86df47b796e0a106641..c294b13fcd90c723c924c8c8ccf2dd0e7874300e 100644 (file)
@@ -297,6 +297,7 @@ var defaultCommonSettings = map[string]interface{}{
        "tabsize":        float64(4),
        "tabstospaces":   false,
        "useprimary":     true,
+       "wordwrap":       false,
 }
 
 func GetInfoBarOffset() int {
index 5f95054fb832cf47f4fc3a642c5f5e0603884586..4ccd8d8d3668991f4a122ff4e0d04cbb37117246 100644 (file)
@@ -420,6 +420,8 @@ func (w *BufWindow) displayBuffer() {
        }
 
        softwrap := b.Settings["softwrap"].(bool)
+       wordwrap := softwrap && b.Settings["wordwrap"].(bool)
+
        tabsize := util.IntOpt(b.Settings["tabsize"])
        colorcolumn := util.IntOpt(b.Settings["colorcolumn"])
 
@@ -571,15 +573,31 @@ func (w *BufWindow) displayBuffer() {
                        }
                }
 
+               type glyph struct {
+                       r     rune
+                       combc []rune
+                       style tcell.Style
+                       width int
+               }
+
+               var word []glyph
+               if wordwrap {
+                       word = make([]glyph, 0, w.bufWidth)
+               } else {
+                       word = make([]glyph, 0, 1)
+               }
+               wordwidth := 0
+
                totalwidth := w.StartCol - nColsBeforeStart
                for len(line) > 0 {
                        r, combc, size := util.DecodeCharacter(line)
+                       line = line[size:]
 
-                       curStyle, _ = w.getStyle(curStyle, bloc)
+                       loc := buffer.Loc{X: bloc.X + len(word), Y: bloc.Y}
+                       curStyle, _ = w.getStyle(curStyle, loc)
 
                        width := 0
 
-                       char := ' '
                        switch r {
                        case '\t':
                                ts := tabsize - (totalwidth % tabsize)
@@ -587,17 +605,27 @@ func (w *BufWindow) displayBuffer() {
                                totalwidth += ts
                        default:
                                width = runewidth.RuneWidth(r)
-                               char = '@'
                                totalwidth += width
                        }
 
-                       // If a wide rune does not fit in the window
-                       if vloc.X+width > maxWidth && vloc.X > w.gutterOffset {
+                       word = append(word, glyph{r, combc, curStyle, width})
+                       wordwidth += width
+
+                       // Collect a complete word to know its width.
+                       // If wordwrap is off, every single character is a complete "word".
+                       if wordwrap {
+                               if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
+                                       continue
+                               }
+                       }
+
+                       // If a word (or just a wide rune) does not fit in the window
+                       if vloc.X+wordwidth > maxWidth && vloc.X > w.gutterOffset {
                                for vloc.X < maxWidth {
                                        draw(' ', nil, config.DefStyle, false)
                                }
 
-                               // We either stop or we wrap to draw the rune in the next line
+                               // We either stop or we wrap to draw the word in the next line
                                if !softwrap {
                                        break
                                } else {
@@ -609,16 +637,25 @@ func (w *BufWindow) displayBuffer() {
                                }
                        }
 
-                       draw(r, combc, curStyle, true)
+                       for _, r := range word {
+                               draw(r.r, r.combc, r.style, true)
+
+                               // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
+                               if r.width > 1 {
+                                       char := ' '
+                                       if r.r != '\t' {
+                                               char = '@'
+                                       }
 
-                       // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
-                       if width > 1 {
-                               for i := 1; i < width; i++ {
-                                       draw(char, nil, curStyle, false)
+                                       for i := 1; i < r.width; i++ {
+                                               draw(char, nil, r.style, false)
+                                       }
                                }
+                               bloc.X++
                        }
-                       bloc.X++
-                       line = line[size:]
+
+                       word = word[:0]
+                       wordwidth = 0
 
                        // If we reach the end of the window then we either stop or we wrap for softwrap
                        if vloc.X >= maxWidth {
index 2cbfe1750f516caf8cf39b4746d3316327c79ca7..0597f061604e97e37ac0377ef8f7e2179228c72b 100644 (file)
@@ -56,14 +56,19 @@ func (w *BufWindow) getVLocFromLoc(loc buffer.Loc) VLoc {
                return vloc
        }
 
+       wordwrap := w.Buf.Settings["wordwrap"].(bool)
        tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
 
        line := w.Buf.LineBytes(loc.Y)
        x := 0
        totalwidth := 0
 
+       wordwidth := 0
+       wordoffset := 0
+
        for len(line) > 0 {
                r, _, size := util.DecodeCharacter(line)
+               line = line[size:]
 
                width := 0
                switch r {
@@ -76,19 +81,37 @@ func (w *BufWindow) getVLocFromLoc(loc buffer.Loc) VLoc {
                        totalwidth += width
                }
 
-               // If a wide rune does not fit in the window
-               if vloc.VisualX+width > w.bufWidth && vloc.VisualX > 0 {
+               wordwidth += width
+
+               // Collect a complete word to know its width.
+               // If wordwrap is off, every single character is a complete "word".
+               if wordwrap {
+                       if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
+                               if x < loc.X {
+                                       wordoffset += width
+                                       x++
+                               }
+                               continue
+                       }
+               }
+
+               // If a word (or just a wide rune) does not fit in the window
+               if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
                        vloc.Row++
                        vloc.VisualX = 0
                }
 
                if x == loc.X {
+                       vloc.VisualX += wordoffset
                        return vloc
                }
                x++
-               line = line[size:]
 
-               vloc.VisualX += width
+               vloc.VisualX += wordwidth
+
+               wordwidth = 0
+               wordoffset = 0
+
                if vloc.VisualX >= w.bufWidth {
                        vloc.Row++
                        vloc.VisualX = 0
@@ -104,6 +127,7 @@ func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {
                return loc
        }
 
+       wordwrap := w.Buf.Settings["wordwrap"].(bool)
        tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
 
        line := w.Buf.LineBytes(svloc.Line)
@@ -111,8 +135,17 @@ func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {
 
        totalwidth := 0
 
+       var widths []int
+       if wordwrap {
+               widths = make([]int, 0, w.bufWidth)
+       } else {
+               widths = make([]int, 0, 1)
+       }
+       wordwidth := 0
+
        for len(line) > 0 {
                r, _, size := util.DecodeCharacter(line)
+               line = line[size:]
 
                width := 0
                switch r {
@@ -125,21 +158,40 @@ func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {
                        totalwidth += width
                }
 
-               // If a wide rune does not fit in the window
-               if vloc.VisualX+width > w.bufWidth && vloc.VisualX > 0 {
+               widths = append(widths, width)
+               wordwidth += width
+
+               // Collect a complete word to know its width.
+               // If wordwrap is off, every single character is a complete "word".
+               if wordwrap {
+                       if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
+                               continue
+                       }
+               }
+
+               // If a word (or just a wide rune) does not fit in the window
+               if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
                        if vloc.Row == svloc.Row {
+                               if wordwrap {
+                                       // it's a word, not a wide rune
+                                       loc.X--
+                               }
                                return loc
                        }
                        vloc.Row++
                        vloc.VisualX = 0
                }
 
-               vloc.VisualX += width
-               if vloc.Row == svloc.Row && vloc.VisualX > svloc.VisualX {
-                       return loc
+               for i := range widths {
+                       vloc.VisualX += widths[i]
+                       if vloc.Row == svloc.Row && vloc.VisualX > svloc.VisualX {
+                               return loc
+                       }
+                       loc.X++
                }
-               loc.X++
-               line = line[size:]
+
+               widths = widths[:0]
+               wordwidth = 0
 
                if vloc.VisualX >= w.bufWidth {
                        vloc.Row++
index 3805de7ffdc18d368177e249888427bb6021a89f..491eaba5f2b8029a7c487db61c00895a01a0f5bd 100644 (file)
@@ -365,6 +365,11 @@ Here are the available options:
 
        default value: `true`
 
+* `wordwrap`: wrap long lines by words, i.e. break at spaces. This option
+   only does anything if `softwrap` is on.
+
+       default value: `false`
+
 * `xterm`: micro will assume that the terminal it is running in conforms to
   `xterm-256color` regardless of what the `$TERM` variable actually contains.
    Enabling this option may cause unwanted effects if your terminal in fact