1
0
duplikat dari https://github.com/quitesimpleorg/hs9001.git synced 2025-07-08 23:24:56 +02:00

2 Melakukan

Penulis SHA1 Pesan Tanggal
6e4ed9a1cd Implement bash CTRL-R reverse history
Fork customized liner into own repo bash.
Rename -being and -end options to -after and -before.
2021-08-08 11:37:48 +02:00
26ababc93e Begin liner integration 2021-08-08 11:31:41 +02:00
5 mengubah file dengan 53 tambahan dan 156 penghapusan

Melihat File

@ -2,33 +2,15 @@
hs9001 (history search 9001) is an easy, quite simple bash history enhancement. It simply writes all
your bash commands into an sqlite database. You can then search this database.
It improves over bash's built-in history mechanism as it'll aggregate the shell history of all open shells,
timestamp them and also record additional information such as the directory a command was executed in.
## Usage / Examples
### Search
```
hs [search terms]
```
You can further filter with options like `-cwd`, `-after` and so on...
For a full list, see `-help`.
```
hs -cwd .
```
Lists all commands ever entered in this directory
```
hs -after yesterday -cwd . git
```
Lists all git commands in the current directory which have been entered today.
Also, it (by default) replaces bash's built-in CTRL-R mechanism, so hs9001's database will be used instead
of bash's limited history files.
## Install
### From source
```
go build
#move hs9001 to a PATH location
```
### Debian / Ubuntu
Latest release can be installed using apt
```
@ -46,26 +28,31 @@ apk update
apk add hs9001
```
### From source
```
go build
#move hs9001 to a PATH location
```
## Setup / Config
### Setup / Config
Add this to .bashrc
```
eval "$(hs9001 bash-enable)"
```
This will also create a `hs`alias so you have to type less in everyday usage.
By default, every system user gets his own database. You can override this by setting the environment variable
for all users that should write to your unified database.
```
export HS9001_DB_PATH="/home/db/history.sqlite"
```
## Usage
### Search
```
hs9001 search [search terms]
```
It is recommended to create an alias for search to make life easier, e. g.:
```
alias searchh='hs9001 search'
```

Melihat File

@ -2,10 +2,8 @@ package main
import (
"database/sql"
"hs9001/liner"
"io"
"log"
"path/filepath"
"strings"
)
@ -13,34 +11,13 @@ type history struct {
conn *sql.DB
}
func createSearchOpts(query string, mode int) searchopts {
func (h *history) GetHistoryByPrefix(prefix string) (ph []string) {
opts := searchopts{}
o := "DESC"
opts.order = &o
lim := 100
opts.limit = &lim
opts.command = &query
switch mode {
case liner.ModeGlobal:
break
case liner.ModeWorkdir:
workdir, err := filepath.Abs(".")
if err != nil {
panic(err)
}
opts.workdir = &workdir
default:
panic("Invalid mode supplied")
}
return opts
}
func (h *history) GetHistoryByPrefix(prefix string, mode int) (ph []string) {
cmdquery := prefix + "%"
opts := createSearchOpts(cmdquery, mode)
opts.order = "DESC"
cmdqry := prefix + "%"
opts.command = &cmdqry
results := search(h.conn, opts)
for e := results.Back(); e != nil; e = e.Prev() {
for e := results.Front(); e != nil; e = e.Next() {
entry, ok := e.Value.(*HistoryEntry)
if !ok {
log.Panic("Failed to retrieve entries")
@ -49,13 +26,13 @@ func (h *history) GetHistoryByPrefix(prefix string, mode int) (ph []string) {
}
return
}
func (h *history) GetHistoryByPattern(pattern string, mode int) (ph []string, pos []int) {
cmdquery := "%" + pattern + "%"
opts := createSearchOpts(cmdquery, mode)
func (h *history) GetHistoryByPattern(pattern string) (ph []string, pos []int) {
opts := searchopts{}
opts.order = "DESC"
cmdqry := "%" + pattern + "%"
opts.command = &cmdqry
results := search(h.conn, opts)
for e := results.Back(); e != nil; e = e.Prev() {
for e := results.Front(); e != nil; e = e.Next() {
entry, ok := e.Value.(*HistoryEntry)
if !ok {
log.Panic("Failed to retrieve entries")

Melihat File

@ -37,8 +37,8 @@ type HistoryProvider interface {
WriteHistory(w io.Writer) (num int, err error)
AppendHistory(item string)
ClearHistory()
GetHistoryByPrefix(prefix string, mode int) (ph []string)
GetHistoryByPattern(pattern string, mode int) (ph []string, pos []int)
GetHistoryByPrefix(prefix string) (ph []string)
GetHistoryByPattern(pattern string) (ph []string, pos []int)
RLock()
RUnlock()
}
@ -95,11 +95,11 @@ func (s *State) AppendHistory(item string) {
func (s *State) ClearHistory() {
s.historyProvider.ClearHistory()
}
func (s *State) getHistoryByPrefix(prefix string, mode int) (ph []string) {
return s.historyProvider.GetHistoryByPrefix(prefix, mode)
func (s *State) getHistoryByPrefix(prefix string) (ph []string) {
return s.historyProvider.GetHistoryByPrefix(prefix)
}
func (s *State) getHistoryByPattern(pattern string, mode int) (ph []string, pos []int) {
return s.historyProvider.GetHistoryByPattern(pattern, mode)
func (s *State) getHistoryByPattern(pattern string) (ph []string, pos []int) {
return s.historyProvider.GetHistoryByPattern(pattern)
}
// SetHistoryProvider allows you to set a custom provider

Melihat File

@ -90,11 +90,6 @@ const (
tabReverse
)
const (
ModeGlobal = iota
ModeWorkdir
)
func (s *State) refresh(prompt []rune, buf []rune, pos int) error {
if s.columns == 0 {
return ErrInternal
@ -416,26 +411,8 @@ func (s *State) tabComplete(p []rune, line []rune, pos int) ([]rune, int, interf
// reverse intelligent search, implements a bash-like history search.
func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, interface{}, error) {
modeSelect := false
currentMode := ModeGlobal
getPrompt := func(arg string) string {
prompt := ""
switch currentMode {
case ModeWorkdir:
prompt = "(reverse:cwd)`%s': "
case ModeGlobal:
prompt = "(reverse:global)`%s': "
default:
panic("Invalid mode")
}
if modeSelect {
prompt = "(select mode)`%s': "
}
return fmt.Sprintf(prompt, arg)
}
err := s.refresh([]rune(getPrompt("")), origLine, origPos)
p := "(reverse-i-search)`': "
err := s.refresh([]rune(p), origLine, origPos)
if err != nil {
return origLine, origPos, rune(esc), err
}
@ -449,10 +426,11 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
getLine := func() ([]rune, []rune, int) {
search := string(line)
return []rune(getPrompt(search)), []rune(foundLine), foundPos
prompt := "(reverse-i-search)`%s': "
return []rune(fmt.Sprintf(prompt, search)), []rune(foundLine), foundPos
}
history, positions := s.getHistoryByPattern(string(line), currentMode)
history, positions := s.getHistoryByPattern(string(line))
historyPos := len(history) - 1
for {
@ -472,8 +450,6 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
} else {
s.doBeep()
}
case ctrlA:
modeSelect = true
case ctrlS: // Search forward
if historyPos < len(history)-1 && historyPos >= 0 {
historyPos++
@ -491,7 +467,7 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
pos -= n
// For each char deleted, display the last matching line of history
history, positions := s.getHistoryByPattern(string(line), currentMode)
history, positions := s.getHistoryByPattern(string(line))
historyPos = len(history) - 1
if len(history) > 0 {
foundLine = history[historyPos]
@ -504,28 +480,17 @@ func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, inter
case ctrlG: // Cancel
return origLine, origPos, rune(esc), err
case tab, cr, lf, ctrlB, ctrlD, ctrlE, ctrlF, ctrlK,
case tab, cr, lf, ctrlA, ctrlB, ctrlD, ctrlE, ctrlF, ctrlK,
ctrlL, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ:
fallthrough
case 0, ctrlC, esc, 28, 29, 30, 31:
return []rune(foundLine), foundPos, next, err
default:
if modeSelect {
switch v {
case 'g':
currentMode = ModeGlobal
case 'w':
currentMode = ModeWorkdir
}
modeSelect = false
break
}
line = append(line[:pos], append([]rune{v}, line[pos:]...)...)
pos++
// For each keystroke typed, display the last matching line of history
history, positions = s.getHistoryByPattern(string(line), currentMode)
history, positions = s.getHistoryByPattern(string(line))
historyPos = len(history) - 1
if len(history) > 0 {
foundLine = history[historyPos]
@ -761,7 +726,7 @@ mainLoop:
case ctrlP: // up
historyAction = true
if historyStale {
historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
historyPrefix = s.getHistoryByPrefix(string(line))
historyPos = len(historyPrefix)
historyStale = false
}
@ -779,7 +744,7 @@ mainLoop:
case ctrlN: // down
historyAction = true
if historyStale {
historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
historyPrefix = s.getHistoryByPrefix(string(line))
historyPos = len(historyPrefix)
historyStale = false
}
@ -947,7 +912,7 @@ mainLoop:
case up:
historyAction = true
if historyStale {
historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
historyPrefix = s.getHistoryByPrefix(string(line))
historyPos = len(historyPrefix)
historyStale = false
}
@ -964,7 +929,7 @@ mainLoop:
case down:
historyAction = true
if historyStale {
historyPrefix = s.getHistoryByPrefix(string(line), ModeGlobal)
historyPrefix = s.getHistoryByPrefix(string(line))
historyPos = len(historyPrefix)
historyStale = false
}

40
main.go
Melihat File

@ -157,8 +157,7 @@ type searchopts struct {
after *time.Time
before *time.Time
retval *int
order *string
limit *int
order string
}
func search(conn *sql.DB, opts searchopts) list.List {
@ -189,18 +188,7 @@ func search(conn *sql.DB, opts searchopts) list.List {
args = append(args, opts.retval)
}
sb.WriteString("ORDER BY timestamp ")
if opts.order != nil {
sb.WriteString(*opts.order)
sb.WriteRune(' ')
} else {
sb.WriteString("ASC ")
}
if opts.limit != nil {
sb.WriteString("LIMIT ")
sb.WriteString(strconv.Itoa(*opts.limit))
sb.WriteRune(' ')
}
sb.WriteString("ASC ")
queryStmt := sb.String()
@ -321,7 +309,6 @@ func main() {
PROMPT_COMMAND='hs9001 add -ret $? "$(history 1)"'
bind -x '"\C-r": " READLINE_LINE=$(hs9001 bash-ctrlr 3>&1 1>&2 2>&3) READLINE_POINT=0"'
fi
alias hs='hs9001 search'
`)
case "bash-disable":
fmt.Printf("unset PROMPT_COMMAND\n")
@ -365,8 +352,7 @@ func main() {
q := strings.Join(args, " ")
opts := searchopts{}
o := "ASC"
opts.order = &o
opts.order = "ASC"
if q != "" {
cmd := "%" + q + "%"
opts.command = &cmd
@ -398,31 +384,13 @@ func main() {
results := search(conn, opts)
previousCmd := ""
fi, err := os.Stdout.Stat()
if err != nil {
panic(err)
}
//Don't print colors if output is piped
printColors := true
if (fi.Mode() & os.ModeCharDevice) == 0 {
printColors = false
}
for e := results.Front(); e != nil; e = e.Next() {
entry, ok := e.Value.(*HistoryEntry)
if !ok {
log.Panic("Failed to retrieve entries")
}
if !distinct || previousCmd != entry.cmd {
prefix := ""
postfix := ""
if printColors && entry.retval != 0 {
prefix = "\033[38;5;88m"
postfix = "\033[0m"
}
fmt.Printf("%s%s%s\n", prefix, entry.cmd, postfix)
fmt.Printf("%s\n", entry.cmd)
}
previousCmd = entry.cmd
}