Add tags (#791)
* initial tags implementation * add tag to complete.go * add gTagsPath to os_windows.go * change behaviour to match other commands * add tag-toggle to complete.go * add tag and tag-toggle to docs * address feedback about tags
This commit is contained in:
parent
2f9d840eda
commit
5a5628d667
@ -51,6 +51,10 @@ func run() {
|
||||
app.ui.echoerrf("reading marks file: %s", err)
|
||||
}
|
||||
|
||||
if err := app.nav.readTags(); err != nil {
|
||||
app.ui.echoerrf("reading tags file: %s", err)
|
||||
}
|
||||
|
||||
if err := app.readHistory(); err != nil {
|
||||
app.ui.echoerrf("reading history file: %s", err)
|
||||
}
|
||||
|
@ -69,6 +69,8 @@ var (
|
||||
"source",
|
||||
"push",
|
||||
"delete",
|
||||
"tag",
|
||||
"tag-toggle",
|
||||
}
|
||||
|
||||
gOptWords = []string{
|
||||
|
20
doc.go
20
doc.go
@ -71,6 +71,8 @@ The following commands are provided by lf:
|
||||
mark-save (modal) (default 'm')
|
||||
mark-load (modal) (default "'")
|
||||
mark-remove (modal) (default `"`)
|
||||
tag-toggle (default t)
|
||||
tag
|
||||
|
||||
The following command line commands are provided by lf:
|
||||
|
||||
@ -149,6 +151,7 @@ The following options can be used to customize the behavior of lf:
|
||||
waitmsg string (default 'Press any key to continue')
|
||||
wrapscan bool (default on)
|
||||
wrapscroll bool (default off)
|
||||
tagfmt string (default "\033[31m%s\033[0m")
|
||||
|
||||
The following environment variables are exported for shell commands:
|
||||
|
||||
@ -233,6 +236,11 @@ History file should be located at:
|
||||
unix ~/.local/share/lf/history
|
||||
windows C:\Users\<user>\AppData\Local\lf\history
|
||||
|
||||
Tags file should be located at:
|
||||
|
||||
unix ~/.local/share/lf/tags
|
||||
windows C:\Users\<user>\AppData\Local\lf\tags
|
||||
|
||||
You can configure the default values of following variables to change these
|
||||
locations:
|
||||
|
||||
@ -457,6 +465,14 @@ A special bookmark "'" holds the previous directory after a 'mark-load', 'cd', o
|
||||
|
||||
Remove a bookmark assigned to the given key.
|
||||
|
||||
tag
|
||||
|
||||
Tag a file with a single width character given in the argument.
|
||||
|
||||
tag-toggle (modal) (default 't')
|
||||
|
||||
Tag a file with a single width character given in the argument if the file is untagged, otherwise remove the tag.
|
||||
|
||||
Command Line Commands
|
||||
|
||||
This section shows information about command line commands.
|
||||
@ -782,6 +798,10 @@ Searching can wrap around the file list.
|
||||
|
||||
Scrolling can wrap around the file list.
|
||||
|
||||
tagfmt string (default "\033[31m%s\033[0m")
|
||||
|
||||
Format string of the tags.
|
||||
|
||||
Environment Variables
|
||||
|
||||
The following variables are exported for shell commands:
|
||||
|
21
docstring.go
21
docstring.go
@ -76,6 +76,8 @@ The following commands are provided by lf:
|
||||
mark-save (modal) (default 'm')
|
||||
mark-load (modal) (default "'")
|
||||
mark-remove (modal) (default '"')
|
||||
tag-toggle (default t)
|
||||
tag
|
||||
|
||||
The following command line commands are provided by lf:
|
||||
|
||||
@ -154,6 +156,7 @@ The following options can be used to customize the behavior of lf:
|
||||
waitmsg string (default 'Press any key to continue')
|
||||
wrapscan bool (default on)
|
||||
wrapscroll bool (default off)
|
||||
tagfmt string (default "\033[31m%s\033[0m")
|
||||
|
||||
The following environment variables are exported for shell commands:
|
||||
|
||||
@ -240,6 +243,11 @@ History file should be located at:
|
||||
unix ~/.local/share/lf/history
|
||||
windows C:\Users\<user>\AppData\Local\lf\history
|
||||
|
||||
Tags file should be located at:
|
||||
|
||||
unix ~/.local/share/lf/tags
|
||||
windows C:\Users\<user>\AppData\Local\lf\tags
|
||||
|
||||
You can configure the default values of following variables to change these
|
||||
locations:
|
||||
|
||||
@ -482,6 +490,15 @@ or 'select' command.
|
||||
|
||||
Remove a bookmark assigned to the given key.
|
||||
|
||||
tag
|
||||
|
||||
Tag a file with a single width character given in the argument.
|
||||
|
||||
tag-toggle (modal) (default 't')
|
||||
|
||||
Tag a file with a single width character given in the argument if the file
|
||||
is untagged, otherwise remove the tag.
|
||||
|
||||
|
||||
Command Line Commands
|
||||
|
||||
@ -839,6 +856,10 @@ Searching can wrap around the file list.
|
||||
|
||||
Scrolling can wrap around the file list.
|
||||
|
||||
tagfmt string (default "\033[31m%s\033[0m")
|
||||
|
||||
Format string of the tags.
|
||||
|
||||
|
||||
Environment Variables
|
||||
|
||||
|
44
eval.go
44
eval.go
@ -983,6 +983,50 @@ func (e *callExpr) eval(app *app, args []string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
case "tag-toggle":
|
||||
if !app.nav.init {
|
||||
return
|
||||
}
|
||||
|
||||
if len(e.args) == 0 {
|
||||
app.ui.echoerr("tag-toggle: missing default tag")
|
||||
} else if err := app.nav.toggleTag(e.args[0]); err != nil {
|
||||
app.ui.echoerrf("tag-toggle: %s", err)
|
||||
} else if err := app.nav.writeTags(); err != nil {
|
||||
app.ui.echoerrf("tag-toggle: %s", err)
|
||||
}
|
||||
|
||||
if gSingleMode {
|
||||
if err := app.nav.sync(); err != nil {
|
||||
app.ui.echoerrf("tag-toggle: %s", err)
|
||||
}
|
||||
} else {
|
||||
if err := remote("send sync"); err != nil {
|
||||
app.ui.echoerrf("tag-toggle: %s", err)
|
||||
}
|
||||
}
|
||||
case "tag":
|
||||
if !app.nav.init {
|
||||
return
|
||||
}
|
||||
|
||||
if len(e.args) == 0 {
|
||||
app.ui.echoerr("tag: missing tag")
|
||||
} else if err := app.nav.tag(e.args[0]); err != nil {
|
||||
app.ui.echoerrf("tag: %s", err)
|
||||
} else if err := app.nav.writeTags(); err != nil {
|
||||
app.ui.echoerrf("tag: %s", err)
|
||||
}
|
||||
|
||||
if gSingleMode {
|
||||
if err := app.nav.sync(); err != nil {
|
||||
app.ui.echoerrf("tag: %s", err)
|
||||
}
|
||||
} else {
|
||||
if err := remote("send sync"); err != nil {
|
||||
app.ui.echoerrf("tag: %s", err)
|
||||
}
|
||||
}
|
||||
case "invert":
|
||||
if !app.nav.init {
|
||||
return
|
||||
|
28
lf.1
28
lf.1
@ -86,6 +86,8 @@ The following commands are provided by lf:
|
||||
mark-save (modal) (default 'm')
|
||||
mark-load (modal) (default "'")
|
||||
mark-remove (modal) (default `"`)
|
||||
tag-toggle (default t)
|
||||
tag
|
||||
.EE
|
||||
.PP
|
||||
The following command line commands are provided by lf:
|
||||
@ -168,6 +170,7 @@ The following options can be used to customize the behavior of lf:
|
||||
waitmsg string (default 'Press any key to continue')
|
||||
wrapscan bool (default on)
|
||||
wrapscroll bool (default off)
|
||||
tagfmt string (default "\e033[31m%s\e033[0m")
|
||||
.EE
|
||||
.PP
|
||||
The following environment variables are exported for shell commands:
|
||||
@ -271,6 +274,13 @@ History file should be located at:
|
||||
windows C:\eUsers\e<user>\eAppData\eLocal\elf\ehistory
|
||||
.EE
|
||||
.PP
|
||||
Tags file should be located at:
|
||||
.PP
|
||||
.EX
|
||||
unix ~/.local/share/lf/tags
|
||||
windows C:\eUsers\e<user>\eAppData\eLocal\elf\etags
|
||||
.EE
|
||||
.PP
|
||||
You can configure the default values of following variables to change these locations:
|
||||
.PP
|
||||
.EX
|
||||
@ -564,6 +574,18 @@ Change the current directory to the bookmark assigned to the given key. A specia
|
||||
.EE
|
||||
.PP
|
||||
Remove a bookmark assigned to the given key.
|
||||
.PP
|
||||
.EX
|
||||
tag
|
||||
.EE
|
||||
.PP
|
||||
Tag a file with a single width character given in the argument.
|
||||
.PP
|
||||
.EX
|
||||
tag-toggle (modal) (default 't')
|
||||
.EE
|
||||
.PP
|
||||
Tag a file with a single width character given in the argument if the file is untagged, otherwise remove the tag.
|
||||
.SH COMMAND LINE COMMANDS
|
||||
This section shows information about command line commands. These should be mostly compatible with readline keybindings. A character refers to a unicode code point, a word consists of letters and digits, and a unix word consists of any non-blank characters.
|
||||
.PP
|
||||
@ -955,6 +977,12 @@ Searching can wrap around the file list.
|
||||
.EE
|
||||
.PP
|
||||
Scrolling can wrap around the file list.
|
||||
.PP
|
||||
.EX
|
||||
tagfmt string (default "\e033[31m%s\e033[0m")
|
||||
.EE
|
||||
.PP
|
||||
Format string of the tags.
|
||||
.SH ENVIRONMENT VARIABLES
|
||||
The following variables are exported for shell commands: These are referred with a '$' prefix on POSIX shells (e.g. '$f'), between '%' characters on Windows cmd (e.g. '%f%'), and with a '$env:' prefix on Windows powershell (e.g. '$env:f').
|
||||
.PP
|
||||
|
97
nav.go
97
nav.go
@ -61,6 +61,7 @@ func readdir(path string) ([]*file, error) {
|
||||
fpath := filepath.Join(path, fname)
|
||||
|
||||
lstat, err := os.Lstat(fpath)
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
@ -373,6 +374,7 @@ type nav struct {
|
||||
renameOldPath string
|
||||
renameNewPath string
|
||||
selections map[string]int
|
||||
tags map[string]string
|
||||
selectionInd int
|
||||
height int
|
||||
find string
|
||||
@ -495,6 +497,7 @@ func newNav(height int) *nav {
|
||||
saves: make(map[string]bool),
|
||||
marks: make(map[string]string),
|
||||
selections: make(map[string]int),
|
||||
tags: make(map[string]string),
|
||||
selectionInd: 0,
|
||||
height: height,
|
||||
jumpList: make([]string, 0),
|
||||
@ -961,6 +964,46 @@ func (nav *nav) toggle() {
|
||||
nav.toggleSelection(curr.path)
|
||||
}
|
||||
|
||||
func (nav *nav) toggleTagSelection(path string, tag string) {
|
||||
if _, ok := nav.tags[path]; ok {
|
||||
delete(nav.tags, path)
|
||||
} else {
|
||||
nav.tags[path] = tag
|
||||
}
|
||||
}
|
||||
|
||||
func (nav *nav) toggleTag(tag string) error {
|
||||
list, err := nav.currFileOrSelections()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if printLength(tag) != 1 {
|
||||
return errors.New("tag should be single width character")
|
||||
}
|
||||
|
||||
for _, path := range list {
|
||||
nav.toggleTagSelection(path, tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nav *nav) tag(tag string) error {
|
||||
curr, err := nav.currFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if printLength(tag) != 1 {
|
||||
return errors.New("tag should be single width character")
|
||||
}
|
||||
|
||||
nav.tags[curr.path] = tag
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nav *nav) invert() {
|
||||
dir := nav.currDir()
|
||||
for _, f := range dir.files {
|
||||
@ -1268,6 +1311,7 @@ func (nav *nav) sync() error {
|
||||
nav.marks[tmp] = v
|
||||
}
|
||||
}
|
||||
err = nav.readTags()
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1561,6 +1605,59 @@ func (nav *nav) writeMarks() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nav *nav) readTags() error {
|
||||
nav.tags = make(map[string]string)
|
||||
f, err := os.Open(gTagsPath)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening tags file: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
toks := strings.SplitN(scanner.Text(), ":", 2)
|
||||
if _, ok := nav.tags[toks[0]]; !ok {
|
||||
nav.tags[toks[0]] = toks[1]
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return fmt.Errorf("reading tags file: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nav *nav) writeTags() error {
|
||||
if err := os.MkdirAll(filepath.Dir(gTagsPath), os.ModePerm); err != nil {
|
||||
return fmt.Errorf("creating data directory: %s", err)
|
||||
}
|
||||
|
||||
f, err := os.Create(gTagsPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating tags file: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var keys []string
|
||||
for k := range nav.tags {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
_, err = f.WriteString(fmt.Sprintf("%s:%s\n", k, nav.tags[k]))
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing tags file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nav *nav) currDir() *dir {
|
||||
return nav.dirs[len(nav.dirs)-1]
|
||||
}
|
||||
|
3
opts.go
3
opts.go
@ -74,6 +74,7 @@ var gOpts struct {
|
||||
cmds map[string]expr
|
||||
sortType sortType
|
||||
tempmarks string
|
||||
tagfmt string
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -120,6 +121,7 @@ func init() {
|
||||
gOpts.shellopts = nil
|
||||
gOpts.sortType = sortType{naturalSort, dirfirstSort}
|
||||
gOpts.tempmarks = "'"
|
||||
gOpts.tagfmt = "\033[31m%s\033[0m"
|
||||
|
||||
gOpts.keys = make(map[string]expr)
|
||||
|
||||
@ -147,6 +149,7 @@ func init() {
|
||||
gOpts.keys["["] = &callExpr{"jump-prev", nil, 1}
|
||||
gOpts.keys["]"] = &callExpr{"jump-next", nil, 1}
|
||||
gOpts.keys["<space>"] = &listExpr{[]expr{&callExpr{"toggle", nil, 1}, &callExpr{"down", nil, 1}}, 1}
|
||||
gOpts.keys["t"] = &listExpr{[]expr{&callExpr{"tag-toggle", []string{"*"}, 1}, &callExpr{"down", nil, 1}}, 1}
|
||||
gOpts.keys["v"] = &callExpr{"invert", nil, 1}
|
||||
gOpts.keys["u"] = &callExpr{"unselect", nil, 1}
|
||||
gOpts.keys["y"] = &callExpr{"copy", nil, 1}
|
||||
|
2
os.go
2
os.go
@ -35,6 +35,7 @@ var (
|
||||
gIconsPaths []string
|
||||
gFilesPath string
|
||||
gMarksPath string
|
||||
gTagsPath string
|
||||
gHistoryPath string
|
||||
)
|
||||
|
||||
@ -98,6 +99,7 @@ func init() {
|
||||
|
||||
gFilesPath = filepath.Join(data, "lf", "files")
|
||||
gMarksPath = filepath.Join(data, "lf", "marks")
|
||||
gTagsPath = filepath.Join(data, "lf", "tags")
|
||||
gHistoryPath = filepath.Join(data, "lf", "history")
|
||||
|
||||
runtime := os.Getenv("XDG_RUNTIME_DIR")
|
||||
|
@ -33,6 +33,7 @@ var (
|
||||
gColorsPaths []string
|
||||
gIconsPaths []string
|
||||
gFilesPath string
|
||||
gTagsPath string
|
||||
gMarksPath string
|
||||
gHistoryPath string
|
||||
)
|
||||
@ -82,6 +83,7 @@ func init() {
|
||||
|
||||
gFilesPath = filepath.Join(data, "lf", "files")
|
||||
gMarksPath = filepath.Join(data, "lf", "marks")
|
||||
gTagsPath = filepath.Join(data, "lf", "tags")
|
||||
gHistoryPath = filepath.Join(data, "lf", "history")
|
||||
}
|
||||
|
||||
|
18
ui.go
18
ui.go
@ -324,7 +324,7 @@ func fileInfo(f *file, d *dir) string {
|
||||
return info
|
||||
}
|
||||
|
||||
func (win *win) printDir(screen tcell.Screen, dir *dir, selections map[string]int, saves map[string]bool, colors styleMap, icons iconMap) {
|
||||
func (win *win) printDir(screen tcell.Screen, dir *dir, selections map[string]int, saves map[string]bool, tags map[string]string, colors styleMap, icons iconMap) {
|
||||
if win.w < 5 || dir == nil {
|
||||
return
|
||||
}
|
||||
@ -449,6 +449,18 @@ func (win *win) printDir(screen tcell.Screen, dir *dir, selections map[string]in
|
||||
s = append(s, ' ')
|
||||
|
||||
win.print(screen, lnwidth+1, i, st, string(s))
|
||||
|
||||
tag, ok := tags[path]
|
||||
if ok {
|
||||
st = st.Reverse(false)
|
||||
fg, bg, _ := st.Decompose()
|
||||
|
||||
if i == dir.pos {
|
||||
win.print(screen, lnwidth+1, i, st.Background(fg), fmt.Sprintf(gOpts.tagfmt, tag))
|
||||
} else {
|
||||
win.print(screen, lnwidth+1, i, st.Background(bg), fmt.Sprintf(gOpts.tagfmt, tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -839,7 +851,7 @@ func (ui *ui) draw(nav *nav) {
|
||||
|
||||
doff := len(nav.dirs) - length
|
||||
for i := 0; i < length; i++ {
|
||||
ui.wins[woff+i].printDir(ui.screen, nav.dirs[doff+i], nav.selections, nav.saves, ui.styles, ui.icons)
|
||||
ui.wins[woff+i].printDir(ui.screen, nav.dirs[doff+i], nav.selections, nav.saves, nav.tags, ui.styles, ui.icons)
|
||||
}
|
||||
|
||||
switch ui.cmdPrefix {
|
||||
@ -873,7 +885,7 @@ func (ui *ui) draw(nav *nav) {
|
||||
preview := ui.wins[len(ui.wins)-1]
|
||||
|
||||
if curr.IsDir() {
|
||||
preview.printDir(ui.screen, ui.dirPrev, nav.selections, nav.saves, ui.styles, ui.icons)
|
||||
preview.printDir(ui.screen, ui.dirPrev, nav.selections, nav.saves, nav.tags, ui.styles, ui.icons)
|
||||
} else if curr.Mode().IsRegular() {
|
||||
preview.printReg(ui.screen, ui.regPrev)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user