Add option to search for directories through fzgo itself
This commit is contained in:
parent
430ff540f5
commit
a005938761
202
src/data.go
202
src/data.go
|
@ -4,55 +4,57 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/list"
|
"github.com/charmbracelet/bubbles/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Ranking struct {
|
type Ranking struct {
|
||||||
items []string
|
items []string
|
||||||
indexes []int
|
indexes []int
|
||||||
scores []float64
|
scores []float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Ranking) Len() int { return len(r.items) }
|
func (r Ranking) Len() int { return len(r.items) }
|
||||||
func (r Ranking) Swap(i, j int) {
|
func (r Ranking) Swap(i, j int) {
|
||||||
r.indexes[i], r.indexes[j] = r.indexes[j], r.indexes[i]
|
r.indexes[i], r.indexes[j] = r.indexes[j], r.indexes[i]
|
||||||
}
|
}
|
||||||
func (r Ranking) Less(i, j int) bool {
|
func (r Ranking) Less(i, j int) bool {
|
||||||
return r.scores[r.indexes[i]] > r.scores[r.indexes[j]]
|
return r.scores[r.indexes[i]] > r.scores[r.indexes[j]]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to compute the score for a given string
|
// Function to compute the score for a given string
|
||||||
func computeScore(term string, s string) float64 {
|
func computeScore(term string, s string) float64 {
|
||||||
term = strings.ToLower(term)
|
term = strings.ToLower(term)
|
||||||
a := strings.ToLower(s)
|
a := strings.ToLower(s)
|
||||||
splits := strings.Split(a, "/")
|
splits := strings.Split(a, "/")
|
||||||
term_splits := strings.Split(term, "/")
|
term_splits := strings.Split(term, "/")
|
||||||
|
|
||||||
score := 0.0
|
score := 0.0
|
||||||
used := make([]bool, len(term_splits))
|
used := make([]bool, len(term_splits))
|
||||||
for i, term := range term_splits {
|
for i, term := range term_splits {
|
||||||
if used[i] {
|
if used[i] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for j, c := range splits {
|
for j, c := range splits {
|
||||||
if len(term) == 0 {
|
if len(term) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(c, term) {
|
if strings.Contains(c, term) {
|
||||||
used[i] = true
|
used[i] = true
|
||||||
score += 1.0/float64(j)
|
score += 1.0 / float64(j)
|
||||||
splits = splits[j+1:]
|
splits = splits[j+1:]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if score > 0 {
|
if score > 0 {
|
||||||
score += 1.0/float64(strings.Count(a, "/")+1)
|
score += 1.0 / float64(strings.Count(a, "/")+1)
|
||||||
}
|
}
|
||||||
return score
|
return score
|
||||||
}
|
}
|
||||||
|
|
||||||
func rank(term string, targets []string) []int {
|
func rank(term string, targets []string) []int {
|
||||||
|
@ -88,17 +90,17 @@ func rank(term string, targets []string) []int {
|
||||||
scores[result.index] = result.score
|
scores[result.index] = result.score
|
||||||
}
|
}
|
||||||
|
|
||||||
indexes := make([]int, len(targets))
|
indexes := make([]int, len(targets))
|
||||||
for i := range targets {
|
for i := range targets {
|
||||||
indexes[i] = i
|
indexes[i] = i
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Stable(Ranking{targets, indexes, scores})
|
sort.Stable(Ranking{targets, indexes, scores})
|
||||||
return indexes
|
return indexes
|
||||||
}
|
}
|
||||||
|
|
||||||
func Filter(term string, targets []string) []list.Rank {
|
func Filter(term string, targets []string) []list.Rank {
|
||||||
indexes := rank(term, targets)
|
indexes := rank(term, targets)
|
||||||
result := make([]list.Rank, len(targets))
|
result := make([]list.Rank, len(targets))
|
||||||
for i := range targets {
|
for i := range targets {
|
||||||
result[i] = list.Rank{
|
result[i] = list.Rank{
|
||||||
|
@ -110,58 +112,110 @@ func Filter(term string, targets []string) []list.Rank {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPathsFromFile(path string) []string {
|
func getPathsFromFile(path string) []string {
|
||||||
paths := []string{}
|
paths := []string{}
|
||||||
|
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Error opening file:", err)
|
fmt.Fprintln(os.Stderr, "Error opening file:", err)
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
defer file.Close() // Ensure the file is closed when we're done
|
defer file.Close() // Ensure the file is closed when we're done
|
||||||
|
|
||||||
// Create a scanner to read the file line by line
|
// Create a scanner to read the file line by line
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
paths = append(paths, line)
|
paths = append(paths, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for errors during scanning
|
// Check for errors during scanning
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Error reading file:", err)
|
fmt.Fprintln(os.Stderr, "Error reading file:", err)
|
||||||
}
|
}
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPathsFromStdin() []string {
|
func getPathsFromStdin() []string {
|
||||||
paths := []string{}
|
paths := []string{}
|
||||||
|
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text() // Get the current line
|
line := scanner.Text() // Get the current line
|
||||||
paths = append(paths, line)
|
paths = append(paths, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for errors during scanning
|
// Check for errors during scanning
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Error reading from stdin:", err)
|
fmt.Fprintln(os.Stderr, "Error reading from stdin:", err)
|
||||||
}
|
}
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findSubdirectories(root string) ([]string, []string, error) {
|
||||||
|
var dirs []string
|
||||||
|
var files []string
|
||||||
|
|
||||||
func getListItems() []list.Item {
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
items := []list.Item{}
|
if err != nil {
|
||||||
// paths := getPathsFromFile("/home/user/.cache/fzy_paths_d")
|
return err
|
||||||
paths := getPathsFromStdin()
|
}
|
||||||
for _, path := range paths {
|
// Skip hidden files and directories (those starting with a dot)
|
||||||
if len(path) == 0 {
|
baseName := filepath.Base(path)
|
||||||
continue
|
is_dotfile := strings.HasPrefix(baseName, ".")
|
||||||
}
|
if info.IsDir() {
|
||||||
items = append(items, item{path})
|
if is_dotfile && filepath.Clean(path) != filepath.Clean(root) {
|
||||||
}
|
return filepath.SkipDir
|
||||||
return items
|
}
|
||||||
|
dirs = append(dirs, path)
|
||||||
|
} else {
|
||||||
|
if is_dotfile {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error walking directory tree: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirs, files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getListItemsFromDir(root string, searchDirs bool, searchFiles bool) []list.Item {
|
||||||
|
items := []list.Item{}
|
||||||
|
dirs, files, err := findSubdirectories(root)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
if searchFiles {
|
||||||
|
for _, file := range files {
|
||||||
|
items = append(items, item{file})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if searchDirs {
|
||||||
|
for _, dir := range dirs {
|
||||||
|
items = append(items, item{dir})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func getListItemsFromStdin() []list.Item {
|
||||||
|
items := []list.Item{}
|
||||||
|
// paths := getPathsFromFile("/home/user/.cache/fzy_paths_d")
|
||||||
|
paths := getPathsFromStdin()
|
||||||
|
for _, path := range paths {
|
||||||
|
if len(path) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, item{path})
|
||||||
|
}
|
||||||
|
return items
|
||||||
}
|
}
|
||||||
|
|
36
src/main.go
36
src/main.go
|
@ -1,22 +1,48 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/list"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/muesli/termenv"
|
"github.com/muesli/termenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
lipgloss.DefaultRenderer().SetColorProfile(termenv.TrueColor)
|
lipgloss.DefaultRenderer().SetColorProfile(termenv.TrueColor)
|
||||||
p := tea.NewProgram(initialModel(getListItems()), tea.WithOutput(os.Stderr))
|
|
||||||
m, err := p.Run();
|
// Command-line flags
|
||||||
|
searchFiles := flag.Bool("f", false, "Search for files")
|
||||||
|
searchDirs := flag.Bool("d", false, "Search for directories")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if !*searchFiles && !*searchDirs {
|
||||||
|
*searchFiles = true
|
||||||
|
*searchDirs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather list items for each path
|
||||||
|
var items []list.Item
|
||||||
|
|
||||||
|
// Read paths from command line arguments
|
||||||
|
if len(flag.Args()) < 1 {
|
||||||
|
items = getListItemsFromStdin()
|
||||||
|
} else {
|
||||||
|
paths := flag.Args()
|
||||||
|
for _, path := range paths {
|
||||||
|
items = append(items, getListItemsFromDir(path, *searchDirs, *searchFiles)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := tea.NewProgram(initialModel(items), tea.WithOutput(os.Stderr))
|
||||||
|
m, err := p.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(m.View())
|
fmt.Println(m.View())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
119
src/tui.go
119
src/tui.go
|
@ -2,20 +2,21 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/list"
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/charmbracelet/bubbles/list"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type item struct {
|
type item struct {
|
||||||
value string
|
value string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i item) FilterValue() string { return i.value }
|
func (i item) FilterValue() string { return i.value }
|
||||||
|
|
||||||
type itemDelegate struct{}
|
type itemDelegate struct{}
|
||||||
|
|
||||||
func (d itemDelegate) Height() int { return 1 }
|
func (d itemDelegate) Height() int { return 1 }
|
||||||
|
@ -27,7 +28,7 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
str := fmt.Sprintf("%s", i.value)
|
str := i.value
|
||||||
|
|
||||||
fn := lipgloss.NewStyle().PaddingLeft(2).Render
|
fn := lipgloss.NewStyle().PaddingLeft(2).Render
|
||||||
if index == m.Index() {
|
if index == m.Index() {
|
||||||
|
@ -41,80 +42,78 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
ignoreInput := false
|
ignoreInput := false
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case tea.KeyCtrlC, tea.KeyEsc:
|
case tea.KeyCtrlC, tea.KeyEsc:
|
||||||
m.quitting = true;
|
m.quitting = true
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
case tea.KeyEnter:
|
case tea.KeyEnter:
|
||||||
i, ok := m.list.SelectedItem().(item)
|
i, ok := m.list.SelectedItem().(item)
|
||||||
if ok {
|
if ok {
|
||||||
m.choice = i.value
|
m.choice = i.value
|
||||||
}
|
}
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
case tea.KeyUp, tea.KeyCtrlK:
|
case tea.KeyUp, tea.KeyCtrlK:
|
||||||
m.list.CursorUp()
|
m.list.CursorUp()
|
||||||
ignoreInput = true
|
ignoreInput = true
|
||||||
case tea.KeyDown, tea.KeyCtrlJ:
|
case tea.KeyDown, tea.KeyCtrlJ:
|
||||||
m.list.CursorDown()
|
m.list.CursorDown()
|
||||||
ignoreInput = true
|
ignoreInput = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// We handle errors just like any other message
|
// We handle errors just like any other message
|
||||||
case errMsg:
|
case errMsg:
|
||||||
m.err = msg
|
m.err = msg
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
m.textInput.Width = msg.Width
|
m.textInput.Width = msg.Width
|
||||||
m.list.SetWidth(msg.Width)
|
m.list.SetWidth(msg.Width)
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ignoreInput {
|
if ignoreInput {
|
||||||
return m, cmd
|
return m, cmd
|
||||||
}
|
}
|
||||||
cmds := []tea.Cmd{}
|
cmds := []tea.Cmd{}
|
||||||
|
|
||||||
m.textInput, cmd = m.textInput.Update(msg)
|
m.textInput, cmd = m.textInput.Update(msg)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
if !m.list.SettingFilter() {
|
||||||
if !m.list.SettingFilter() {
|
m.list, cmd = m.list.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("/")})
|
||||||
m.list, cmd = m.list.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("/")})
|
cmds = append(cmds, cmd)
|
||||||
cmds = append(cmds, cmd)
|
}
|
||||||
}
|
|
||||||
m.list, cmd = m.list.Update(msg)
|
m.list, cmd = m.list.Update(msg)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
return m, tea.Batch(cmds...)
|
return m, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) View() string {
|
func (m model) View() string {
|
||||||
if m.choice != "" {
|
if m.choice != "" {
|
||||||
return m.choice
|
return m.choice
|
||||||
}
|
}
|
||||||
if m.quitting {
|
if m.quitting {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s\n",
|
return fmt.Sprintf("%s\n",
|
||||||
m.list.View(),
|
m.list.View(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
errMsg error
|
errMsg error
|
||||||
)
|
)
|
||||||
|
|
||||||
type model struct {
|
type model struct {
|
||||||
textInput textinput.Model
|
textInput textinput.Model
|
||||||
list list.Model
|
list list.Model
|
||||||
choice string
|
choice string
|
||||||
quitting bool
|
quitting bool
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,23 +121,23 @@ func initialModel(items []list.Item) model {
|
||||||
const defaultWidth = 20
|
const defaultWidth = 20
|
||||||
|
|
||||||
l := list.New(items, itemDelegate{}, defaultWidth, 14)
|
l := list.New(items, itemDelegate{}, defaultWidth, 14)
|
||||||
l.SetShowPagination(false)
|
l.SetShowPagination(false)
|
||||||
l.SetShowStatusBar(false)
|
l.SetShowStatusBar(false)
|
||||||
l.SetShowHelp(false)
|
l.SetShowHelp(false)
|
||||||
l.SetShowTitle(false)
|
l.SetShowTitle(false)
|
||||||
l.SetFilteringEnabled(true)
|
l.SetFilteringEnabled(true)
|
||||||
l.SetShowFilter(true)
|
l.SetShowFilter(true)
|
||||||
l.Styles.TitleBar = lipgloss.NewStyle()
|
l.Styles.TitleBar = lipgloss.NewStyle()
|
||||||
ti := textinput.New()
|
ti := textinput.New()
|
||||||
ti.Focus()
|
ti.Focus()
|
||||||
ti.CharLimit = 4096
|
ti.CharLimit = 4096
|
||||||
ti.Width = 20
|
ti.Width = 20
|
||||||
l.FilterInput = ti
|
l.FilterInput = ti
|
||||||
l.Filter = Filter
|
l.Filter = Filter
|
||||||
|
|
||||||
return model{
|
return model{
|
||||||
list: l,
|
list: l,
|
||||||
err: nil,
|
err: nil,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue