Skip to content

Commit

Permalink
Add top bar; fix display responsiveness; fix broken chars
Browse files Browse the repository at this point in the history
  • Loading branch information
giulianopz committed May 12, 2023
1 parent a371cbf commit 6c7f012
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 49 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ log
/newscanoe

dist/
version.txt
1 change: 1 addition & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ project_name: newscanoe
before:
hooks:
- go mod tidy
- go generate ./...

builds:
- env:
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
## Newscanoe

Newscanoe aims to be a minimal reimplementation of the glorious [newsboat](https://newsboat.org/):
- only for Linux terminal emulators (at the moment, at least) supporting [VT100](https://en.wikipedia.org/wiki/VT100) terminal escape sequences
- only for Linux (at the moment, at least) terminal emulators supporting [VT100](https://en.wikipedia.org/wiki/VT100) terminal escape sequences
- written in Go but rigorously nonglamorous (i.e. vim-like style)
- meant to be lighter and easier to build from source or to distribute (in the future) as a traditional distribution-dependent (e.g. rpm/deb) or independent (e.g. Snap, Flatpak, or AppImage) package.

### Configuration

The only config file consists of urls of RSS/Atom feeds listed line-by-line (see the [urls sample file](./assets/urls)) and located in the directory `$XDG_CONFIG_HOME/newscanoe` or `$HOME/.config/newscanoe`.

If such file does not already exist, it will be created at the first execution of the app and the user will be able to manually insert a url by typing `a` or by creating/modifying such file with any text editor.
If such file does not already exist, it will be created at the first execution of the app and you will be able to manually insert a url by typing `a`, Otherwise create such file with any text editor.

Once loaded, feeds are cached in the directory `$XDG_CACHE_HOME/newscanoe` or `$HOME/.cache/newscanoe`.

Currently, the app uses just the default foreground colour (+ red/green as feedbacks to user actions) of your terminal theme to highlight the different UI components.

### Keybindings

Supported key bindings:
Expand All @@ -26,10 +28,10 @@ Supported key bindings:
- `^`, `v`, move the cursor to the previous/next row
- `Page Up`, `Page Down`, move the cursor to the previous/next page
- `a`, insert manually a new feed url, then:
- `ENTER`, append it to the config file
- `<`, `>`, move the cursor to the previous/next char
- `BACKSPACE`, cancel last char
- `CANC`, cancel currently highlighted char
- `ENTER`, append the typed in url in the config file

### Installation

Expand All @@ -45,7 +47,7 @@ Build from source:

Or download the latest pre-compiled binary from [GitHub](https://github.com/giulianopz/newscanoe/releases) and then install it in your PATH.

[![asciicast](https://asciinema.org/a/QeAvNtiPK86vTbSWpoC6grymg.svg)](https://asciinema.org/a/QeAvNtiPK86vTbSWpoC6grymg)
[![asciicast](https://asciinema.org/a/GmD6rN1s4vcQVT0xmlYOrlacq.svg)](https://asciinema.org/a/GmD6rN1s4vcQVT0xmlYOrlacq)

### Development

Expand Down
10 changes: 9 additions & 1 deletion Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ tasks:
run:
cmds:
- go run cmd/newscanoe/main.go

build:
cmds:
- go build -o newscanoe cmd/newscanoe/main.go
- go build -o newscanoe --ldflags="-X 'github.com/giulianopz/newscanoe/pkg/app.Version=$(git describe --tags $(git rev-list --tags --max-count=1))'" cmd/newscanoe/main.go

install:
cmds:
- cp ./newscanoe ~/bin/

release:
cmds:
- goreleaser release --debug --snapshot --skip-publish --rm-dist

debug:
cmds:
- go run cmd/newscanoe/main.go -d 2> log

clean:
cmds:
- rm ~/.cache/newscanoe/feeds.gob
11 changes: 8 additions & 3 deletions pkg/app/info.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package app

const (
Name = "newscanoe"
Version = "v0.1.0"
import (
_ "embed"
)

const Name = "newscanoe"

//go:generate bash version.sh
//go:embed version.txt
var Version string
2 changes: 2 additions & 0 deletions pkg/app/version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
printf '%s' "$(git describe --tags $(git rev-list --tags --max-count=1))" > version.txt
100 changes: 75 additions & 25 deletions pkg/display/display.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"unicode/utf8"

"github.com/giulianopz/newscanoe/pkg/ansi"
"github.com/giulianopz/newscanoe/pkg/app"
"github.com/giulianopz/newscanoe/pkg/cache"
"github.com/giulianopz/newscanoe/pkg/util"
"github.com/mmcdole/gofeed"
Expand All @@ -25,8 +26,11 @@ const (
ARTICLE_TEXT
)

// num of lines reserved to bottom bar plus final empty row
const BOTTOM_PADDING = 3
// num of lines reserved to top and bottom bars plus a final empty row
const (
TOP_PADDING = 2
BOTTOM_PADDING = 3
)

// bottom bar messages
const (
Expand All @@ -48,16 +52,17 @@ type display struct {
height int
width int

bottomBarColor int
// color of top and bottom bars
barsColor int
// message displayed in the bottom bar
topBarMsg string
// message displayed in the bottom bar
bottomBarMsg string
// message displayed in the right corner of the bottom bar
bottomRightCorner string

mu sync.Mutex

//TODO use arrays of rune

// dislay raw text
raw [][]byte
// dislay rendered text
Expand Down Expand Up @@ -87,7 +92,7 @@ func New() *display {
startoff: 0,
endoff: 0,
cache: cache.NewCache(),
bottomBarColor: ansi.WHITE,
barsColor: ansi.WHITE,
ListenToKeyStroke: true,
client: &http.Client{
Timeout: 3 * time.Second,
Expand All @@ -97,6 +102,12 @@ func New() *display {
return d
}

func (d *display) setTopMessage(msg string) {
if utf8.RuneCountInString(msg) < (d.width - utf8.RuneCountInString(app.Name) - utf8.RuneCountInString(app.Version) - 2) {
d.topBarMsg = msg
}
}

func (d *display) setBottomMessage(msg string) {
d.bottomBarMsg = msg
}
Expand All @@ -106,7 +117,7 @@ func (d *display) setTmpBottomMessage(t time.Duration, msg string) {
d.setBottomMessage(msg)
go func() {
time.AfterFunc(t, func() {
d.bottomBarColor = ansi.WHITE
d.barsColor = ansi.WHITE
d.setBottomMessage(previous)
})
}()
Expand Down Expand Up @@ -190,7 +201,7 @@ func (d *display) LoadCache() error {
func (d *display) exitEditingMode(color int) {
d.editingMode = false
d.editingBuf = []string{}
d.bottomBarColor = color
d.barsColor = color
}

func (d *display) enterEditingMode() {
Expand All @@ -215,7 +226,30 @@ func (d *display) canBeParsed(url string) bool {

func (d *display) draw(buf *bytes.Buffer) {

nextEndOff := d.startoff + (d.height - BOTTOM_PADDING) - 1
buf.WriteString(ansi.SGR(ansi.REVERSE_COLOR))
buf.WriteString(ansi.SGR(d.barsColor))

padding := d.width - utf8.RuneCountInString(app.Name) - utf8.RuneCountInString(d.topBarMsg) - 2
log.Default().Printf("top-padding: %d", padding)
if padding > 0 {
buf.WriteString(fmt.Sprintf("%s %s %*s\r\n", app.Name, d.topBarMsg, padding, app.Version))
} else {
buf.WriteString(app.Name)
padding = d.width - utf8.RuneCountInString(app.Name) - utf8.RuneCountInString(app.Version)
for i := padding; i > 0; i-- {
buf.WriteString(" ")
}
buf.WriteString(fmt.Sprintf("%s\r\n", app.Version))
}

buf.WriteString(ansi.SGR(ansi.ALL_ATTRIBUTES_OFF))
buf.WriteString(ansi.SGR(ansi.DEFAULT_FG_COLOR))

for k := 0; k < d.width; k++ {
buf.WriteString("-")
}

nextEndOff := d.startoff + (d.height - BOTTOM_PADDING - TOP_PADDING) - 1
if nextEndOff > (len(d.rendered) - 1) {
d.endoff = (len(d.rendered) - 1)
} else {
Expand All @@ -229,7 +263,7 @@ func (d *display) draw(buf *bytes.Buffer) {
}
}

log.Default().Printf("looping from %d to %d\n", d.startoff, d.endoff)
log.Default().Printf("looping from %d to %d: %d\n", d.startoff, d.endoff, d.endoff-d.startoff)
var printed int
for i := d.startoff; i <= d.endoff; i++ {

Expand All @@ -241,28 +275,35 @@ func (d *display) draw(buf *bytes.Buffer) {
// TODO check that the terminal supports Unicode output, before outputting a Unicode character
// if so, the "LANG" env variable should contain "UTF"

runes := utf8.RuneCountInString(string(d.rendered[i]))

if runes > d.width {
log.Default().Printf("runes for line %d exceed screen width: %d\n", i, runes)
continue
line := string(d.rendered[i])
if line == "" {
line = " "
} else {
runes := utf8.RuneCountInString(line)
if runes > d.width {
log.Default().Printf("truncating current line because its length %d exceeda screen width: %d\n", i, runes)
line = util.Truncate(line, d.width)
}
}

_, err := buf.Write(d.rendered[i])
log.Default().Printf("writing to buf line #%d: %q\n", i, line)

_, err := buf.Write([]byte(line))
if err != nil {
log.Default().Printf("cannot write rune %q: %v", d.rendered[i], err)
log.Default().Printf("cannot write byte array %q: %v", []byte(" "), err)
}

buf.WriteString("\r\n")

if i == d.currentRow() && d.currentSection != ARTICLE_TEXT {
buf.WriteString(ansi.SGR(ansi.ALL_ATTRIBUTES_OFF))
buf.WriteString(ansi.SGR(ansi.DEFAULT_FG_COLOR))
}

buf.WriteString("\r\n")
printed++
}

for ; printed < d.height-BOTTOM_PADDING; printed++ {
for ; printed < d.height-BOTTOM_PADDING-TOP_PADDING; printed++ {
buf.WriteString("\r\n")
}

Expand All @@ -271,16 +312,26 @@ func (d *display) draw(buf *bytes.Buffer) {
}
buf.WriteString("\r\n")

d.bottomRightCorner = fmt.Sprintf("%d/%d", d.cy+d.startoff, len(d.rendered))
if DebugMode {
d.bottomRightCorner = fmt.Sprintf("(y:%v,x:%v) (soff:%v, eoff:%v) (h:%v,w:%v)", d.cy, d.cx, d.startoff, d.endoff, d.height, d.width)
} else {
d.bottomRightCorner = fmt.Sprintf("%d/%d", d.cy+d.startoff, len(d.rendered))
}
padding := d.width - utf8.RuneCountInString(d.bottomBarMsg) - 1

padding = d.width - utf8.RuneCountInString(d.bottomBarMsg) - 1
log.Default().Printf("bottom-padding: %d", padding)

buf.WriteString(ansi.SGR(ansi.REVERSE_COLOR))
buf.WriteString(ansi.SGR(d.bottomBarColor))
buf.WriteString(fmt.Sprintf("%s %*s\r\n", d.bottomBarMsg, padding, d.bottomRightCorner))
buf.WriteString(ansi.SGR(d.barsColor))

if padding > 0 {
buf.WriteString(fmt.Sprintf("%s %*s", d.bottomBarMsg, padding, d.bottomRightCorner))
} else {
padding = d.width - utf8.RuneCountInString(d.bottomRightCorner)
for i := padding; i > 0; i-- {
buf.WriteString(" ")
}
buf.WriteString(d.bottomRightCorner)
}

buf.WriteString(ansi.SGR(ansi.ALL_ATTRIBUTES_OFF))
buf.WriteString(ansi.SGR(ansi.DEFAULT_FG_COLOR))
Expand All @@ -306,7 +357,6 @@ func (d *display) RefreshScreen() {

case ARTICLE_TEXT:
d.renderArticleText()

}

d.draw(buf)
Expand Down
10 changes: 5 additions & 5 deletions pkg/display/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ func ctrlPlus(k byte) byte {
func (d *display) moveCursor(dir byte) {
switch dir {
case ARROW_DOWN:
if d.cy < (d.height - BOTTOM_PADDING) {
if d.cy < (d.height - BOTTOM_PADDING - TOP_PADDING) {
if d.currentRow()+1 <= len(d.rendered)-1 && (d.cx-1) <= (len(d.rendered[d.currentRow()+1])-1) {
d.cy++
}
Expand All @@ -284,7 +284,7 @@ func (d *display) scroll(dir byte) {
case PAGE_DOWN:
{
if d.endoff == len(d.rendered)-1 {
d.cy = d.height - BOTTOM_PADDING
d.cy = d.height - BOTTOM_PADDING - TOP_PADDING
return
}

Expand All @@ -296,7 +296,7 @@ func (d *display) scroll(dir byte) {
d.endoff = len(d.rendered) - 1
}

d.cy = d.height - BOTTOM_PADDING
d.cy = d.height - BOTTOM_PADDING - TOP_PADDING
}
case PAGE_UP:
{
Expand All @@ -305,12 +305,12 @@ func (d *display) scroll(dir byte) {
return
}

firstItemInPreviousPage := d.startoff - (d.height - BOTTOM_PADDING)
firstItemInPreviousPage := d.startoff - (d.height - BOTTOM_PADDING - TOP_PADDING)
if firstItemInPreviousPage >= 0 {
d.startoff = firstItemInPreviousPage
} else {
d.startoff = 0
d.endoff = d.height - BOTTOM_PADDING - 1
d.endoff = d.height - BOTTOM_PADDING - TOP_PADDING - 1
}

d.cy = 1
Expand Down
Loading

0 comments on commit 6c7f012

Please sign in to comment.