Skip to content

Commit 4b266c9

Browse files
committed
initial commit
0 parents  commit 4b266c9

21 files changed

+2797
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lf
2+
tags

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2016 Gökçehan Kara
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
all: build
2+
3+
build:
4+
CGO_ENABLED=0 go build -ldflags '-s'
5+
6+
install:
7+
mv lf $(GOPATH)/bin
8+
9+
test:
10+
go test
11+
12+
.PHONY: all test

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# LF
2+
3+
> Please note that this is an experimental file manager.
4+
> One of the most dangerous pieces of software you can play with.
5+
> You may accidentally lose your files or worse so use at your own risk.
6+
7+
> Likewise it is a work in progress.
8+
> You will most likely come across some shameful bugs.
9+
> Also some essentials may not have been implemented yet.
10+
11+
`lf` (as in "list files") is a terminal file manager written in Go.
12+
It is heavily inspired by ranger with some missing and extra features.
13+
Some of the missing features are deliberately ommited
14+
since it is better if they are handled by external tools.
15+
16+
![multicol-screenshot](http://i.imgur.com/DaTUenu.png)
17+
![singlecol-screenshot](http://i.imgur.com/p95xzUj.png)
18+
19+
## Features
20+
21+
- no external runtime dependencies (except for terminfo database)
22+
- fast startup and low memory footprint (due to native code and static binaries)
23+
- server/client architecture to share selection between multiple instances
24+
- custom commands as shell scripts (hence any other language as well)
25+
- sync (waiting and skipping) and async commands
26+
- fully customizable keybindings
27+
28+
## Non-Features
29+
30+
- tabs or windows (handled by the window manager or the terminal multiplexer)
31+
- built-in pager (handled by your pager of choice)
32+
33+
## May-Futures
34+
35+
- enchanced previews (image, pdf etc.)
36+
- bookmarks
37+
- colorschemes
38+
- periodic refresh
39+
- progress bar for file yank/delete paste
40+
41+
## Installation
42+
43+
You can build from the source using:
44+
45+
go get -u github.com/gokcehan/lf
46+
47+
Currently there are no prebuilt binaries provided.
48+
49+
## Usage
50+
51+
After the installation `lf` command should start the application in the current directory.
52+
53+
See [tutorial](doc/tutorial.md) for an introduction to the configuration.
54+
55+
See [reference](doc/reference.md) for the list of keys, options and variables with their default values.
56+
57+
See [etc](etc) directory to integrate `lf` to your shell or editor.
58+
59+
## File Opener
60+
61+
lf does not come bundled with a file opener.
62+
By default it tries to call `xdg-open` from `xdg-utils` package.
63+
You can change the file opener using the opener option (e.g. `:set opener mimeopen`).
64+
Below are a few alternatives you can use:
65+
66+
- [libfile-mimeinfo-perl](https://metacpan.org/release/File-MimeInfo) (executable name is `mimeopen`)
67+
- [rifle](http://ranger.nongnu.org/) (ranger's default file opener)
68+
- [mimeo](http://xyne.archlinux.ca/projects/mimeo/)
69+
- custom (using file extensions and/or mimetypes with `file` command)

app.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"os/exec"
8+
"strings"
9+
)
10+
11+
type App struct {
12+
ui *UI
13+
nav *Nav
14+
}
15+
16+
func waitKey() error {
17+
// TODO: this should be done with termbox somehow
18+
19+
cmd := exec.Command(envShell, "-c", "echo; echo -n 'Press any key to continue'; stty -echo; read -n 1; stty echo; echo")
20+
21+
cmd.Stdin = os.Stdin
22+
cmd.Stdout = os.Stdout
23+
cmd.Stderr = os.Stderr
24+
25+
err := cmd.Run()
26+
if err != nil {
27+
return fmt.Errorf("waiting key: %s", err)
28+
}
29+
30+
return nil
31+
}
32+
33+
func (app *App) handleInp() {
34+
for {
35+
if gExitFlag {
36+
log.Print("bye!")
37+
return
38+
}
39+
e := app.ui.getExpr()
40+
if e == nil {
41+
continue
42+
}
43+
e.eval(app, nil)
44+
app.ui.draw(app.nav)
45+
}
46+
}
47+
48+
func (app *App) exportVars() {
49+
dir := app.nav.currDir()
50+
51+
var envFile string
52+
if len(dir.fi) != 0 {
53+
envFile = app.nav.currPath()
54+
}
55+
56+
marks := app.nav.currMarks()
57+
58+
envFiles := strings.Join(marks, ":")
59+
60+
os.Setenv("f", envFile)
61+
os.Setenv("fs", envFiles)
62+
63+
if len(marks) == 0 {
64+
os.Setenv("fx", envFile)
65+
} else {
66+
os.Setenv("fx", envFiles)
67+
}
68+
}
69+
70+
// This function is used to run a command in shell. Following modes are used:
71+
//
72+
// Prefix Wait Async Stdin/Stdout/Stderr UI action (before/after)
73+
// $ No No Yes Do nothing and then sync
74+
// ! Yes No Yes pause and then resume
75+
// & No Yes No Do nothing
76+
//
77+
// Waiting async commands are not used for now.
78+
func (app *App) runShell(s string, args []string, wait bool, async bool) {
79+
app.exportVars()
80+
81+
if len(gOpts.ifs) != 0 {
82+
s = fmt.Sprintf("IFS='%s'; %s", gOpts.ifs, s)
83+
}
84+
85+
args = append([]string{"-c", s, "--"}, args...)
86+
cmd := exec.Command(envShell, args...)
87+
88+
if !async {
89+
cmd.Stdin = os.Stdin
90+
cmd.Stdout = os.Stdout
91+
cmd.Stderr = os.Stderr
92+
}
93+
94+
if wait {
95+
app.ui.pause()
96+
defer app.ui.resume()
97+
} else {
98+
defer app.ui.sync()
99+
}
100+
101+
defer app.nav.renew(app.ui.wins[0].h)
102+
103+
var err error
104+
if async {
105+
err = cmd.Start()
106+
} else {
107+
err = cmd.Run()
108+
}
109+
110+
if err != nil {
111+
msg := fmt.Sprintf("running shell: %s", err)
112+
app.ui.message = msg
113+
log.Print(msg)
114+
}
115+
116+
if wait {
117+
if err := waitKey(); err != nil {
118+
msg := fmt.Sprintf("waiting shell: %s", err)
119+
app.ui.message = msg
120+
log.Print(msg)
121+
}
122+
}
123+
}

client.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"log"
7+
"net"
8+
"os"
9+
10+
"github.com/nsf/termbox-go"
11+
)
12+
13+
func client() {
14+
logFile, err := os.Create(gLogPath)
15+
if err != nil {
16+
panic(err)
17+
}
18+
defer logFile.Close()
19+
log.SetOutput(logFile)
20+
21+
log.Print("hi!")
22+
23+
if err := termbox.Init(); err != nil {
24+
log.Fatalf("initializing termbox: %s", err)
25+
}
26+
defer termbox.Close()
27+
28+
ui := newUI()
29+
nav := newNav(ui.wins[0].h)
30+
app := &App{ui, nav}
31+
32+
rcFile, err := os.Open(gConfigPath)
33+
if err != nil {
34+
msg := fmt.Sprintf("opening configuration file: %s", err)
35+
app.ui.message = msg
36+
log.Printf(msg)
37+
} else {
38+
app.ui.echoFileInfo(app.nav)
39+
}
40+
defer rcFile.Close()
41+
42+
p := newParser(rcFile)
43+
for p.parse() {
44+
p.expr.eval(app, nil)
45+
}
46+
47+
// TODO: parser error check
48+
49+
app.ui.draw(app.nav)
50+
51+
app.handleInp()
52+
}
53+
54+
func saveFiles(list []string, keep bool) error {
55+
c, err := net.Dial("unix", gSocketPath)
56+
if err != nil {
57+
return fmt.Errorf("dialing to save files: %s", err)
58+
}
59+
defer c.Close()
60+
61+
log.Printf("saving files: %v", list)
62+
63+
fmt.Fprintln(c, "save")
64+
65+
if keep {
66+
fmt.Fprintln(c, "keep")
67+
} else {
68+
fmt.Fprintln(c, "move")
69+
}
70+
71+
for _, f := range list {
72+
fmt.Fprintln(c, f)
73+
}
74+
75+
return nil
76+
}
77+
78+
func loadFiles() (list []string, keep bool, err error) {
79+
c, e := net.Dial("unix", gSocketPath)
80+
if e != nil {
81+
err = fmt.Errorf("dialing to load files: %s", e)
82+
return
83+
}
84+
defer c.Close()
85+
86+
fmt.Fprintln(c, "load")
87+
88+
s := bufio.NewScanner(c)
89+
90+
switch s.Scan(); s.Text() {
91+
case "keep":
92+
keep = true
93+
case "move":
94+
keep = false
95+
default:
96+
err = fmt.Errorf("unexpected option to keep file(s): %s", s.Text())
97+
return
98+
}
99+
100+
for s.Scan() {
101+
list = append(list, s.Text())
102+
}
103+
104+
if s.Err() != nil {
105+
err = fmt.Errorf("scanning file list: %s", s.Err())
106+
return
107+
}
108+
109+
log.Printf("loading files: %v", list)
110+
111+
return
112+
}

doc/reference.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Reference
2+
3+
## Keys
4+
5+
down (default "j")
6+
up (default "k")
7+
updir (default "h")
8+
open (default "l")
9+
quit (default "q")
10+
bot (default "G")
11+
top (default "gg")
12+
read (default ":")
13+
read-shell (default "$")
14+
read-shell-wait (default "!")
15+
read-shell-async (default "&")
16+
search (default "/")
17+
search-back (default "?")
18+
toggle (default "<space>")
19+
yank (default "y")
20+
delete (default "d")
21+
paste (default "p")
22+
redraw (default "<c-l>")
23+
24+
## Options
25+
26+
preview bool (default on)
27+
hidden bool (default off)
28+
tabstop int (default 8)
29+
scrolloff int (default 0)
30+
sortby string (default name)
31+
showinfo string (default none)
32+
opener string (default xdg-open)
33+
ratios string (default 1:2:3)
34+
35+
## Variables
36+
37+
$f current file
38+
$fs marked file(s) (seperated with ':')
39+
$fx current file or marked file(s) if any

0 commit comments

Comments
 (0)