Repository: gokcehan/lf
Pull request #1285 (2023-06-04 03:02:51)

Add `doc` to default keybindings in documentation
Add the `doc` command to the list of default commands/mappings in the documentation for completeness. I think this is intended to be a literal copy of the code from [`os.go`](https://github.com/gokcehan/lf/blob/a9d90bc56044aa5edeb2b45c161af3c0099a3bed/os.go#L159-L167) and [`os_windows.go`](https://github.com/gokcehan/lf/blob/a9d90bc56044aa5edeb2b45c161af3c0099a3bed/os_windows.go#L118-L126).


Patch:
diff --git a/app.go b/app.go
index f05a7a3..4bfaea6 100644
--- a/app.go
+++ b/app.go
@@ -602,26 +602,16 @@ func (app *app) runShell(s string, args []string, prefix string) {
 
 }
 
-func (app *app) runPagerOnText(text io.Reader) {
+func (app *app) runPagerOn(stdin io.Reader) {
 	app.nav.exportFiles()
 	app.ui.exportSizes()
 	exportOpts()
 
 	cmd := shellCommand(envPager, nil)
 
+	cmd.Stdin = stdin
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
 
-	stdin, err := cmd.StdinPipe()
-	if err != nil {
-		app.ui.echoerrf("obtaining stdin pipe: %s", err)
-		return
-	}
-
-	go func() {
-		io.Copy(stdin, text)
-		stdin.Close()
-	}()
-
 	app.runCmdSync(cmd, false)
 }
diff --git a/complete.go b/complete.go
index d2e04dc..125eeac 100644
--- a/complete.go
+++ b/complete.go
@@ -16,6 +16,7 @@ var (
 		"cmap",
 		"cmaps",
 		"cmd",
+		"cmds",
 		"quit",
 		"up",
 		"half-up",
diff --git a/doc.go b/doc.go
index 096cf5f..99e2819 100644
--- a/doc.go
+++ b/doc.go
@@ -81,6 +81,7 @@ The following commands are provided by lf:
 	tag-toggle               (default 't')
 	maps
 	cmaps
+	cmds
 	jumps
 
 The following command line commands are provided by lf:
@@ -208,11 +209,13 @@ The following special shell commands are used to customize the behavior of lf wh
 
 The following commands/keybindings are provided by default:
 
-	Unix                     Windows
-	cmd open &$OPENER "$f"   cmd open &%OPENER% %f%
-	map e $$EDITOR "$f"      map e $%EDITOR% %f%
-	map i $$PAGER "$f"       map i !%PAGER% %f%
-	map w $$SHELL            map w $%SHELL%
+	Unix                          Windows
+	cmd open &$OPENER "$f"        cmd open &%OPENER% %f%
+	map e $$EDITOR "$f"           map e $%EDITOR% %f%
+	map i $$PAGER "$f"            map i !%PAGER% %f%
+	map w $$SHELL                 map w $%SHELL%
+	cmd doc $$lf -doc | $PAGER    cmd doc !%lf% -doc | %PAGER%
+	map <f-1> doc                 map <f-1> doc
 
 The following additional keybindings are provided by default:
 
@@ -620,6 +623,10 @@ Capitalize/uppercase/lowercase the current word and jump to the next word.
 
 List all key mappings in normal mode or command-line editing mode.
 
+	cmds
+
+List all custom commands defined using the `cmd` command
+
 	jumps
 
 List the contents of the jump list, in order of the most recently visited locations.
diff --git a/docstring.go b/docstring.go
index f716670..759260c 100644
--- a/docstring.go
+++ b/docstring.go
@@ -84,6 +84,7 @@ The following commands are provided by lf:
     tag-toggle               (default 't')
     maps
     cmaps
+    cmds
     jumps
 
 The following command line commands are provided by lf:
@@ -212,11 +213,13 @@ when defined:
 
 The following commands/keybindings are provided by default:
 
-    Unix                     Windows
-    cmd open &$OPENER "$f"   cmd open &%OPENER% %f%
-    map e $$EDITOR "$f"      map e $%EDITOR% %f%
-    map i $$PAGER "$f"       map i !%PAGER% %f%
-    map w $$SHELL            map w $%SHELL%
+    Unix                          Windows
+    cmd open &$OPENER "$f"        cmd open &%OPENER% %f%
+    map e $$EDITOR "$f"           map e $%EDITOR% %f%
+    map i $$PAGER "$f"            map i !%PAGER% %f%
+    map w $$SHELL                 map w $%SHELL%
+    cmd doc $$lf -doc | $PAGER    cmd doc !%lf% -doc | %PAGER%
+    map <f-1> doc                 map <f-1> doc
 
 The following additional keybindings are provided by default:
 
@@ -648,6 +651,10 @@ Capitalize/uppercase/lowercase the current word and jump to the next word.
 
 List all key mappings in normal mode or command-line editing mode.
 
+    cmds
+
+List all custom commands defined using the 'cmd' command
+
     jumps
 
 List the contents of the jump list, in order of the most recently visited
diff --git a/etc/icons.example b/etc/icons.example
index 8039c93..404f9e5 100644
--- a/etc/icons.example
+++ b/etc/icons.example
@@ -87,7 +87,7 @@ fi             # FILE
 *.cc            
 *.cp            
 *.c             
-*.cs            
+*.cs            󰌛
 *.h             
 *.hh            
 *.hpp           
@@ -152,10 +152,10 @@ fi             # FILE
 *.elm           
 *.swift         
 *.xcplayground  
-*.tex           ﭨ
-*.r             ﳒ
-*.rproj         鉶
-*.sol           ﲹ
+*.tex           󰙩
+*.r             󰟔
+*.rproj         󰗆
+*.sol           󰡪
 *.pem           
 
 # file names (vim-devicons) (case-insensitive not supported in lf)
@@ -193,7 +193,7 @@ fi             # FILE
 *gemfile                
 *makefile               
 *cmakelists.txt         
-*robots.txt             ﮧ
+*robots.txt             󰚩
 
 # file names (case-sensitive adaptations)
 *Gruntfile.coffee       
diff --git a/eval.go b/eval.go
index 0ddf107..1ece47b 100644
--- a/eval.go
+++ b/eval.go
@@ -2244,64 +2244,60 @@ func (e *callExpr) eval(app *app, args []string) {
 			}
 		case "rename: ":
 			app.ui.cmdPrefix = ""
-			if curr, err := app.nav.currFile(); err != nil {
-				app.ui.echoerrf("rename: %s", err)
-			} else {
-				wd, err := os.Getwd()
-				if err != nil {
-					log.Printf("getting current directory: %s", err)
-					return
-				}
-
-				oldPath := filepath.Join(wd, curr.Name())
-
-				newPath := filepath.Clean(replaceTilde(s))
-				if !filepath.IsAbs(newPath) {
-					newPath = filepath.Join(wd, newPath)
-				}
 
-				if oldPath == newPath {
-					return
-				}
+			curr, err := app.nav.currFile()
+			if err != nil {
+				app.ui.echoerrf("rename: %s", err)
+				return
+			}
+			wd, err := os.Getwd()
+			if err != nil {
+				log.Printf("getting current directory: %s", err)
+				return
+			}
 
-				app.nav.renameOldPath = oldPath
-				app.nav.renameNewPath = newPath
+			oldPath := filepath.Join(wd, curr.Name())
+			newPath := filepath.Clean(replaceTilde(s))
+			if !filepath.IsAbs(newPath) {
+				newPath = filepath.Join(wd, newPath)
+			}
+			if oldPath == newPath {
+				return
+			}
+			app.nav.renameOldPath = oldPath
+			app.nav.renameNewPath = newPath
 
-				newDir := filepath.Dir(newPath)
-				if _, err := os.Stat(newDir); os.IsNotExist(err) {
-					app.ui.cmdPrefix = "create '" + newDir + "' ? [y/N] "
-					return
-				}
+			newDir := filepath.Dir(newPath)
+			if _, err := os.Stat(newDir); os.IsNotExist(err) {
+				app.ui.cmdPrefix = "create '" + newDir + "' ? [y/N] "
+				return
+			}
 
-				oldStat, err := os.Lstat(oldPath)
-				if err != nil {
-					app.ui.echoerrf("rename: %s", err)
-					return
-				}
+			oldStat, err := os.Lstat(oldPath)
+			if err != nil {
+				app.ui.echoerrf("rename: %s", err)
+				return
+			}
+			if newStat, err := os.Lstat(newPath); !os.IsNotExist(err) && !os.SameFile(oldStat, newStat) {
+				app.ui.cmdPrefix = "replace '" + newPath + "' ? [y/N] "
+				return
+			}
 
-				if newStat, err := os.Lstat(newPath); !os.IsNotExist(err) && !os.SameFile(oldStat, newStat) {
-					app.ui.cmdPrefix = "replace '" + newPath + "' ? [y/N] "
-					return
-				}
+			if err := app.nav.rename(); err != nil {
+				app.ui.echoerrf("rename: %s", err)
+				return
+			}
 
-				if err := app.nav.rename(); err != nil {
+			if gSingleMode {
+				app.nav.renew()
+			} else {
+				if err := remote("send load"); err != nil {
 					app.ui.echoerrf("rename: %s", err)
 					return
 				}
-
-				if gSingleMode {
-					app.nav.renew()
-					app.ui.loadFile(app, true)
-				} else {
-					if err := remote("send load"); err != nil {
-						app.ui.echoerrf("rename: %s", err)
-						return
-					}
-				}
-
-				app.ui.loadFile(app, true)
-				app.ui.loadFileInfo(app.nav)
 			}
+			app.ui.loadFile(app, true)
+			app.ui.loadFileInfo(app.nav)
 		default:
 			log.Printf("entering unknown execution prefix: %q", app.ui.cmdPrefix)
 		}
@@ -2347,9 +2343,10 @@ func (e *callExpr) eval(app *app, args []string) {
 			switch app.ui.cmdPrefix {
 			case "!", "$", "%", "&":
 				app.ui.cmdPrefix = ":"
-			case ">", "rename: ":
+			case ">", "rename: ", "filter: ":
 				// Don't mess with programs waiting for input.
-				// Exiting on backspace is also inconvenient for renames since the text field starts out nonempty.
+				// Exiting on backspace is also inconvenient for 'rename' and 'filter',
+				// since the text field can start out nonempty.
 			default:
 				normal(app)
 			}
@@ -2525,12 +2522,14 @@ func (e *callExpr) eval(app *app, args []string) {
 
 		app.ui.cmdAccLeft = acc
 		update(app)
+	case "cmds":
+		app.runPagerOn(listCmds())
 	case "maps":
-		app.runPagerOnText(listBinds(gOpts.keys))
+		app.runPagerOn(listBinds(gOpts.keys))
 	case "cmaps":
-		app.runPagerOnText(listBinds(gOpts.cmdkeys))
+		app.runPagerOn(listBinds(gOpts.cmdkeys))
 	case "jumps":
-		app.runPagerOnText(listJumps(app.nav.jumpList, app.nav.jumpListInd))
+		app.runPagerOn(listJumps(app.nav.jumpList, app.nav.jumpListInd))
 	default:
 		cmd, ok := gOpts.cmds[e.name]
 		if !ok {
diff --git a/lf.1 b/lf.1
index 6de2b89..97f6460 100644
--- a/lf.1
+++ b/lf.1
@@ -96,6 +96,7 @@ The following commands are provided by lf:
     tag-toggle               (default 't')
     maps
     cmaps
+    cmds
     jumps
 .EE
 .PP
@@ -233,11 +234,13 @@ The following special shell commands are used to customize the behavior of lf wh
 The following commands/keybindings are provided by default:
 .PP
 .EX
-    Unix                     Windows
-    cmd open &$OPENER "$f"   cmd open &%OPENER% %f%
-    map e $$EDITOR "$f"      map e $%EDITOR% %f%
-    map i $$PAGER "$f"       map i !%PAGER% %f%
-    map w $$SHELL            map w $%SHELL%
+    Unix                          Windows
+    cmd open &$OPENER "$f"        cmd open &%OPENER% %f%
+    map e $$EDITOR "$f"           map e $%EDITOR% %f%
+    map i $$PAGER "$f"            map i !%PAGER% %f%
+    map w $$SHELL                 map w $%SHELL%
+    cmd doc $$lf -doc | $PAGER    cmd doc !%lf% -doc | %PAGER%
+    map <f-1> doc                 map <f-1> doc
 .EE
 .PP
 The following additional keybindings are provided by default:
@@ -764,6 +767,12 @@ Capitalize/uppercase/lowercase the current word and jump to the next word.
 .PP
 List all key mappings in normal mode or command-line editing mode.
 .PP
+.EX
+    cmds
+.EE
+.PP
+List all custom commands defined using the `cmd` command
+.PP
 .EX
     jumps
 .EE
diff --git a/misc.go b/misc.go
index ea33664..b0303ea 100644
--- a/misc.go
+++ b/misc.go
@@ -31,23 +31,26 @@ func runeSliceWidth(rs []rune) int {
 }
 
 func runeSliceWidthRange(rs []rune, beg, end int) []rune {
+	if beg == end {
+		return []rune{}
+	}
+
 	curr := 0
 	b := 0
+	foundb := false
 	for i, r := range rs {
 		w := runewidth.RuneWidth(r)
-		switch {
-		case curr == beg:
+		if curr >= beg && !foundb {
 			b = i
-		case curr < beg && curr+w > beg:
-			b = i + 1
-		case curr == end:
+			foundb = true
+		}
+		if curr == end || curr+w > end {
 			return rs[b:i]
-		case curr > end:
-			return rs[b : i-1]
 		}
 		curr += w
 	}
-	return nil
+
+	return rs[b:]
 }
 
 // This function is used to escape whitespaces and special characters with
diff --git a/ui.go b/ui.go
index 4c01391..1f8af2c 100644
--- a/ui.go
+++ b/ui.go
@@ -441,8 +441,16 @@ func (win *win) printDir(screen tcell.Screen, dir *dir, context *dirContext, dir
 			}
 		}
 
+		// make space for select marker, and leave another space at the end
+		maxWidth := win.w - lnwidth - 2
+		// make extra space to separate windows if drawbox is not enabled
+		if !gOpts.drawbox {
+			maxWidth -= 1
+		}
+
 		var s []rune
 
+		// leave space for displaying the tag
 		s = append(s, ' ')
 
 		if gOpts.icons {
@@ -450,40 +458,26 @@ func (win *win) printDir(screen tcell.Screen, dir *dir, context *dirContext, dir
 			s = append(s, ' ')
 		}
 
-		for _, r := range f.Name() {
-			s = append(s, r)
-		}
-
-		w := runeSliceWidth(s)
+		maxFilenameWidth := maxWidth - runeSliceWidth(s)
 
-		// make space for select marker, and leave another space at the end
-		maxlength := win.w - lnwidth - 2
-		// make extra space to separate windows if drawbox is not enabled
-		if !gOpts.drawbox {
-			maxlength -= 1
+		info := fileInfo(f, dir)
+		showInfo := len(info) > 0 && 2*len(info) < maxWidth
+		if showInfo {
+			maxFilenameWidth -= len(info)
 		}
 
-		if w > maxlength {
-			s = runeSliceWidthRange(s, 0, maxlength-1)
-			s = append(s, []rune(gOpts.truncatechar)...)
-		} else {
-			for i := 0; i < maxlength-w; i++ {
-				s = append(s, ' ')
-			}
+		filename := []rune(f.Name())
+		if runeSliceWidth(filename) > maxFilenameWidth {
+			filename = runeSliceWidthRange(filename, 0, maxFilenameWidth-1)
+			filename = append(filename, []rune(gOpts.truncatechar)...)
 		}
+		for i := runeSliceWidth(filename); i < maxFilenameWidth; i++ {
+			filename = append(filename, ' ')
+		}
+		s = append(s, filename...)
 
-		info := fileInfo(f, dir)
-
-		if len(info) > 0 && 2*len(info) < maxlength {
-			if w+len(info) > maxlength {
-				s = runeSliceWidthRange(s, 0, maxlength-len(info)-1)
-				s = append(s, []rune(gOpts.truncatechar)...)
-			} else {
-				s = runeSliceWidthRange(s, 0, maxlength-len(info))
-			}
-			for _, r := range info {
-				s = append(s, r)
-			}
+		if showInfo {
+			s = append(s, []rune(info)...)
 		}
 
 		ce := ""
@@ -1052,7 +1046,7 @@ func findBinds(keys map[string]expr, prefix string) (binds map[string]expr, ok b
 	return
 }
 
-func listBinds(binds map[string]expr) *bytes.Buffer {
+func listExprMap(binds map[string]expr, title string) *bytes.Buffer {
 	t := new(tabwriter.Writer)
 	b := new(bytes.Buffer)
 
@@ -1063,7 +1057,7 @@ func listBinds(binds map[string]expr) *bytes.Buffer {
 	sort.Strings(keys)
 
 	t.Init(b, 0, gOpts.tabstop, 2, '\t', 0)
-	fmt.Fprintln(t, "keys\tcommand")
+	fmt.Fprintf(t, "%s\tcommand\n", title)
 	for _, k := range keys {
 		fmt.Fprintf(t, "%s\t%v\n", k, binds[k])
 	}
@@ -1072,6 +1066,14 @@ func listBinds(binds map[string]expr) *bytes.Buffer {
 	return b
 }
 
+func listBinds(binds map[string]expr) *bytes.Buffer {
+	return listExprMap(binds, "keys")
+}
+
+func listCmds() *bytes.Buffer {
+	return listExprMap(gOpts.cmds, "name")
+}
+
 func listJumps(jumps []string, ind int) *bytes.Buffer {
 	t := new(tabwriter.Writer)
 	b := new(bytes.Buffer)


Test patch:
diff --git a/misc_test.go b/misc_test.go
index 3568fa2..aa6f6df 100644
--- a/misc_test.go
+++ b/misc_test.go
@@ -55,21 +55,30 @@ func TestRuneSliceWidthRange(t *testing.T) {
 		end int
 		exp []rune
 	}{
+		{[]rune{}, 0, 0, []rune{}},
 		{[]rune{'a', 'b', 'c', 'd'}, 1, 3, []rune{'b', 'c'}},
 		{[]rune{'a', 'ı', 'b', 'ş'}, 1, 3, []rune{'ı', 'b'}},
 		{[]rune{'世', '界', '世', '界'}, 2, 6, []rune{'界', '世'}},
 		{[]rune{'世', '界', '世', '界'}, 3, 6, []rune{'世'}},
 		{[]rune{'世', '界', '世', '界'}, 2, 5, []rune{'界'}},
 		{[]rune{'世', '界', '世', '界'}, 3, 5, []rune{}},
+		{[]rune{'世', '界', '世', '界'}, 4, 4, []rune{}},
+		{[]rune{'世', '界', '世', '界'}, 5, 5, []rune{}},
+		{[]rune{'世', '界', '世', '界'}, 4, 7, []rune{'世'}},
+		{[]rune{'世', '界', '世', '界'}, 4, 8, []rune{'世', '界'}},
 		{[]rune{'世', 'a', '界', 'ı'}, 2, 5, []rune{'a', '界'}},
 		{[]rune{'世', 'a', '界', 'ı'}, 2, 4, []rune{'a'}},
 		{[]rune{'世', 'a', '界', 'ı'}, 3, 5, []rune{'界'}},
 		{[]rune{'世', 'a', '界', 'ı'}, 3, 4, []rune{}},
+		{[]rune{'世', 'a', '界', 'ı'}, 3, 3, []rune{}},
+		{[]rune{'世', 'a', '界', 'ı'}, 4, 4, []rune{}},
+		{[]rune{'世', 'a', '界', 'ı'}, 4, 6, []rune{'ı'}},
+		{[]rune{'世', 'a', '界', 'ı'}, 5, 6, []rune{'ı'}},
 	}
 
 	for _, test := range tests {
 		if got := runeSliceWidthRange(test.rs, test.beg, test.end); !reflect.DeepEqual(got, test.exp) {
-			t.Errorf("at input '%v' expected '%v' but got '%v'", test.rs, test.rs, got)
+			t.Errorf("at input '%v' expected '%v' but got '%v'", test.rs, test.exp, got)
 		}
 	}
 }