@@ -1,6 +1,7 @@
package code
import (
+ "fmt"
"strings"
"sync"
@@ -15,6 +16,11 @@ import (
"github.com/muesli/termenv"
)
+var (
+ lineDigitStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("239"))
+ lineBarStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("236"))
+)
+
// Code is a code snippet.
type Code struct {
*vp.Viewport
@@ -24,7 +30,11 @@ type Code struct {
renderContext gansi.RenderContext
renderMutex sync.Mutex
styleConfig gansi.StyleConfig
+ showLineNumber bool
+
NoContentStyle lipgloss.Style
+ LineDigitStyle lipgloss.Style
+ LineBarStyle lipgloss.Style
}
// New returns a new Code.
@@ -35,6 +45,8 @@ func New(c common.Common, content, extension string) *Code {
extension: extension,
Viewport: vp.New(c),
NoContentStyle: c.Styles.CodeNoContent.Copy(),
+ LineDigitStyle: lineDigitStyle,
+ LineBarStyle: lineBarStyle,
}
st := common.StyleConfig()
r.styleConfig = st
@@ -46,6 +58,11 @@ func New(c common.Common, content, extension string) *Code {
return r
}
+// SetShowLineNumber sets whether to show line numbers.
+func (r *Code) SetShowLineNumber(show bool) {
+ r.showLineNumber = show
+}
+
// SetSize implements common.Component.
func (r *Code) SetSize(width, height int) {
r.common.SetSize(width, height)
@@ -64,12 +81,16 @@ func (r *Code) Init() tea.Cmd {
w := r.common.Width
c := r.content
if c == "" {
- c = r.NoContentStyle.String()
+ r.Viewport.Model.SetContent(r.NoContentStyle.String())
+ return nil
}
f, err := r.renderFile(r.extension, c, w)
if err != nil {
return common.ErrorCmd(err)
}
+ if r.showLineNumber {
+ f = withLineNumber(f)
+ }
// FIXME: this is a hack to reset formatting at the end of every line.
c = wrap.String(f, w)
s := strings.Split(c, "\n")
@@ -196,3 +217,23 @@ func (r *Code) renderFile(path, content string, width int) (string, error) {
}
return s.String(), nil
}
+
+func withLineNumber(s string) string {
+ lines := strings.Split(s, "\n")
+ // NB: len() is not a particularly safe way to count string width (because
+ // it's counting bytes instead of runes) but in this case it's okay
+ // because we're only dealing with digits, which are one byte each.
+ mll := len(fmt.Sprintf("%d", len(lines)))
+ for i, l := range lines {
+ digit := fmt.Sprintf("%*d", mll, i+1)
+ bar := "│"
+ digit = lineDigitStyle.Render(digit)
+ bar = lineBarStyle.Render(bar)
+ if i < len(lines)-1 || len(l) != 0 {
+ // If the final line was a newline we'll get an empty string for
+ // the final line, so drop the newline altogether.
+ lines[i] = fmt.Sprintf(" %s %s %s", digit, bar, l)
+ }
+ }
+ return strings.Join(lines, "\n")
+}
@@ -28,6 +28,13 @@ var (
errInvalidFile = errors.New("invalid file")
)
+var (
+ lineNo = key.NewBinding(
+ key.WithKeys("l"),
+ key.WithHelp("l", "toggle line numbers"),
+ )
+)
+
// FileItemsMsg is a message that contains a list of files.
type FileItemsMsg []selector.IdentifiableItem
@@ -49,6 +56,7 @@ type Files struct {
currentItem *FileItem
currentContent FileContentMsg
lastSelected []int
+ lineNumber bool
}
// NewFiles creates a new files model.
@@ -58,6 +66,7 @@ func NewFiles(common common.Common) *Files {
code: code.New(common, "", ""),
activeView: filesViewFiles,
lastSelected: make([]int, 0),
+ lineNumber: true,
}
selector := selector.New(common, []selector.IdentifiableItem{}, FileItemDelegate{&common})
selector.SetShowFilter(false)
@@ -70,6 +79,7 @@ func NewFiles(common common.Common) *Files {
selector.KeyMap.NextPage = common.KeyMap.NextPage
selector.KeyMap.PrevPage = common.KeyMap.PrevPage
f.selector = selector
+ f.code.SetShowLineNumber(f.lineNumber)
return f
}
@@ -101,6 +111,7 @@ func (f *Files) ShortHelp() []key.Binding {
f.common.KeyMap.UpDown,
f.common.KeyMap.BackItem,
copyKey,
+ lineNo,
}
default:
return []key.Binding{}
@@ -158,6 +169,7 @@ func (f *Files) FullHelp() [][]key.Binding {
},
{
copyKey,
+ lineNo,
},
}...)
}
@@ -222,6 +234,10 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds = append(cmds, f.deselectItemCmd)
case key.Matches(msg, f.common.KeyMap.Copy):
f.common.Copy.Copy(f.currentContent.content)
+ case key.Matches(msg, lineNo):
+ f.lineNumber = !f.lineNumber
+ f.code.SetShowLineNumber(f.lineNumber)
+ cmds = append(cmds, f.code.SetContent(f.currentContent.content, f.currentContent.ext))
}
}
case tea.WindowSizeMsg: