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" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
|     "sync" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/charmbracelet/bubbles/list" | ||||
| ) | ||||
| 
 | ||||
| type Ranking struct { | ||||
|     items []string | ||||
|     indexes []int | ||||
|     scores []float64 | ||||
| 	items   []string | ||||
| 	indexes []int | ||||
| 	scores  []float64 | ||||
| } | ||||
| 
 | ||||
| func (r Ranking) Len() int { return len(r.items) } | ||||
| 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 { | ||||
|     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
 | ||||
| func computeScore(term string, s string) float64 { | ||||
|     term = strings.ToLower(term) | ||||
|     a := strings.ToLower(s) | ||||
|     splits := strings.Split(a, "/") | ||||
|     term_splits := strings.Split(term, "/") | ||||
| 	term = strings.ToLower(term) | ||||
| 	a := strings.ToLower(s) | ||||
| 	splits := strings.Split(a, "/") | ||||
| 	term_splits := strings.Split(term, "/") | ||||
| 
 | ||||
|     score := 0.0 | ||||
|     used := make([]bool, len(term_splits)) | ||||
|     for i, term := range term_splits { | ||||
|         if used[i] { | ||||
|             continue | ||||
|         } | ||||
|         for j, c := range splits { | ||||
|             if len(term) == 0 { | ||||
|                 continue | ||||
|             } | ||||
|             if strings.Contains(c, term) { | ||||
|                 used[i] = true | ||||
|                 score += 1.0/float64(j) | ||||
|                 splits = splits[j+1:] | ||||
|                 break | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     if score > 0 { | ||||
|         score += 1.0/float64(strings.Count(a, "/")+1) | ||||
|     } | ||||
|     return score | ||||
| 	score := 0.0 | ||||
| 	used := make([]bool, len(term_splits)) | ||||
| 	for i, term := range term_splits { | ||||
| 		if used[i] { | ||||
| 			continue | ||||
| 		} | ||||
| 		for j, c := range splits { | ||||
| 			if len(term) == 0 { | ||||
| 				continue | ||||
| 			} | ||||
| 			if strings.Contains(c, term) { | ||||
| 				used[i] = true | ||||
| 				score += 1.0 / float64(j) | ||||
| 				splits = splits[j+1:] | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if score > 0 { | ||||
| 		score += 1.0 / float64(strings.Count(a, "/")+1) | ||||
| 	} | ||||
| 	return score | ||||
| } | ||||
| 
 | ||||
| func rank(term string, targets []string) []int { | ||||
|  | @ -88,17 +90,17 @@ func rank(term string, targets []string) []int { | |||
| 		scores[result.index] = result.score | ||||
| 	} | ||||
| 
 | ||||
|     indexes := make([]int, len(targets)) | ||||
|     for i := range targets { | ||||
|         indexes[i] = i | ||||
|     } | ||||
| 	indexes := make([]int, len(targets)) | ||||
| 	for i := range targets { | ||||
| 		indexes[i] = i | ||||
| 	} | ||||
| 
 | ||||
|     sort.Stable(Ranking{targets, indexes, scores}) | ||||
|     return indexes | ||||
| 	sort.Stable(Ranking{targets, indexes, scores}) | ||||
| 	return indexes | ||||
| } | ||||
| 
 | ||||
| func Filter(term string, targets []string) []list.Rank { | ||||
|     indexes := rank(term, targets) | ||||
| 	indexes := rank(term, targets) | ||||
| 	result := make([]list.Rank, len(targets)) | ||||
| 	for i := range targets { | ||||
| 		result[i] = list.Rank{ | ||||
|  | @ -110,58 +112,110 @@ func Filter(term string, targets []string) []list.Rank { | |||
| } | ||||
| 
 | ||||
| func getPathsFromFile(path string) []string { | ||||
|     paths := []string{} | ||||
| 	paths := []string{} | ||||
| 
 | ||||
|     file, err := os.Open(path) | ||||
|     if err != nil { | ||||
|         fmt.Fprintln(os.Stderr, "Error opening file:", err) | ||||
|         return paths | ||||
|     } | ||||
|     defer file.Close() // Ensure the file is closed when we're done
 | ||||
| 	file, err := os.Open(path) | ||||
| 	if err != nil { | ||||
| 		fmt.Fprintln(os.Stderr, "Error opening file:", err) | ||||
| 		return paths | ||||
| 	} | ||||
| 	defer file.Close() // Ensure the file is closed when we're done
 | ||||
| 
 | ||||
|     // Create a scanner to read the file line by line
 | ||||
|     scanner := bufio.NewScanner(file) | ||||
|     for scanner.Scan() { | ||||
|         line := scanner.Text() | ||||
|         if len(line) == 0 { | ||||
|             continue | ||||
|         } | ||||
|         paths = append(paths, line) | ||||
|     } | ||||
| 	// Create a scanner to read the file line by line
 | ||||
| 	scanner := bufio.NewScanner(file) | ||||
| 	for scanner.Scan() { | ||||
| 		line := scanner.Text() | ||||
| 		if len(line) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		paths = append(paths, line) | ||||
| 	} | ||||
| 
 | ||||
|     // Check for errors during scanning
 | ||||
|     if err := scanner.Err(); err != nil { | ||||
|         fmt.Fprintln(os.Stderr, "Error reading file:", err) | ||||
|     } | ||||
|     return paths | ||||
| 	// Check for errors during scanning
 | ||||
| 	if err := scanner.Err(); err != nil { | ||||
| 		fmt.Fprintln(os.Stderr, "Error reading file:", err) | ||||
| 	} | ||||
| 	return paths | ||||
| } | ||||
| 
 | ||||
| func getPathsFromStdin() []string { | ||||
|     paths := []string{} | ||||
| 	paths := []string{} | ||||
| 
 | ||||
| 	scanner := bufio.NewScanner(os.Stdin) | ||||
| 	for scanner.Scan() { | ||||
| 		line := scanner.Text() // Get the current line
 | ||||
|         paths = append(paths, line) | ||||
| 		paths = append(paths, line) | ||||
| 	} | ||||
| 
 | ||||
| 	// Check for errors during scanning
 | ||||
| 	if err := scanner.Err(); err != nil { | ||||
| 		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 { | ||||
|     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 | ||||
| 	err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		// Skip hidden files and directories (those starting with a dot)
 | ||||
| 		baseName := filepath.Base(path) | ||||
| 		is_dotfile := strings.HasPrefix(baseName, ".") | ||||
| 		if info.IsDir() { | ||||
| 			if is_dotfile && filepath.Clean(path) != filepath.Clean(root) { | ||||
| 				return filepath.SkipDir | ||||
| 			} | ||||
| 			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 | ||||
| 
 | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"github.com/charmbracelet/bubbles/list" | ||||
| 	tea "github.com/charmbracelet/bubbletea" | ||||
| 	"github.com/charmbracelet/lipgloss" | ||||
| 	"github.com/muesli/termenv" | ||||
| ) | ||||
| 
 | ||||
| func main() { | ||||
|     lipgloss.DefaultRenderer().SetColorProfile(termenv.TrueColor) | ||||
| 	p := tea.NewProgram(initialModel(getListItems()), tea.WithOutput(os.Stderr)) | ||||
|     m, err := p.Run(); | ||||
| 	lipgloss.DefaultRenderer().SetColorProfile(termenv.TrueColor) | ||||
| 
 | ||||
| 	// 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 { | ||||
| 		log.Fatal(err) | ||||
| 	} else { | ||||
|         fmt.Println(m.View()) | ||||
|     } | ||||
| 		fmt.Println(m.View()) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
							
								
								
									
										119
									
								
								src/tui.go
								
								
								
								
							
							
						
						
									
										119
									
								
								src/tui.go
								
								
								
								
							|  | @ -2,20 +2,21 @@ package main | |||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
|     "io" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/charmbracelet/bubbles/list" | ||||
| 	"github.com/charmbracelet/bubbles/textinput" | ||||
| 	tea "github.com/charmbracelet/bubbletea" | ||||
|     "github.com/charmbracelet/lipgloss" | ||||
|     "github.com/charmbracelet/bubbles/list" | ||||
| 	"github.com/charmbracelet/lipgloss" | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| type item struct { | ||||
|     value string | ||||
| 	value string | ||||
| } | ||||
| 
 | ||||
| func (i item) FilterValue() string { return i.value } | ||||
| 
 | ||||
| type itemDelegate struct{} | ||||
| 
 | ||||
| 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 | ||||
| 	} | ||||
| 
 | ||||
| 	str := fmt.Sprintf("%s", i.value) | ||||
| 	str := i.value | ||||
| 
 | ||||
| 	fn := lipgloss.NewStyle().PaddingLeft(2).Render | ||||
| 	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) { | ||||
| 	var cmd tea.Cmd | ||||
|     ignoreInput := false | ||||
| 	ignoreInput := false | ||||
| 
 | ||||
| 	switch msg := msg.(type) { | ||||
|     case tea.KeyMsg: | ||||
|         switch msg.Type { | ||||
|         case tea.KeyCtrlC, tea.KeyEsc: | ||||
|             m.quitting = true; | ||||
|             return m, tea.Quit | ||||
|         case tea.KeyEnter: | ||||
|             i, ok := m.list.SelectedItem().(item) | ||||
|             if ok { | ||||
|                 m.choice = i.value | ||||
|             } | ||||
|             return m, tea.Quit | ||||
|         case tea.KeyUp, tea.KeyCtrlK: | ||||
|             m.list.CursorUp() | ||||
|             ignoreInput = true | ||||
|         case tea.KeyDown, tea.KeyCtrlJ: | ||||
|             m.list.CursorDown() | ||||
|             ignoreInput = true | ||||
|         } | ||||
| 	case tea.KeyMsg: | ||||
| 		switch msg.Type { | ||||
| 		case tea.KeyCtrlC, tea.KeyEsc: | ||||
| 			m.quitting = true | ||||
| 			return m, tea.Quit | ||||
| 		case tea.KeyEnter: | ||||
| 			i, ok := m.list.SelectedItem().(item) | ||||
| 			if ok { | ||||
| 				m.choice = i.value | ||||
| 			} | ||||
| 			return m, tea.Quit | ||||
| 		case tea.KeyUp, tea.KeyCtrlK: | ||||
| 			m.list.CursorUp() | ||||
| 			ignoreInput = true | ||||
| 		case tea.KeyDown, tea.KeyCtrlJ: | ||||
| 			m.list.CursorDown() | ||||
| 			ignoreInput = true | ||||
| 		} | ||||
| 
 | ||||
|     // We handle errors just like any other message
 | ||||
|     case errMsg: | ||||
|         m.err = msg | ||||
|         return m, nil | ||||
| 	// We handle errors just like any other message
 | ||||
| 	case errMsg: | ||||
| 		m.err = msg | ||||
| 		return m, nil | ||||
| 
 | ||||
|     case tea.WindowSizeMsg: | ||||
|         m.textInput.Width = msg.Width | ||||
|         m.list.SetWidth(msg.Width) | ||||
|         return m, nil | ||||
|     } | ||||
| 	case tea.WindowSizeMsg: | ||||
| 		m.textInput.Width = msg.Width | ||||
| 		m.list.SetWidth(msg.Width) | ||||
| 		return m, nil | ||||
| 	} | ||||
| 
 | ||||
|     if ignoreInput { | ||||
|         return m, cmd | ||||
|     } | ||||
|     cmds := []tea.Cmd{} | ||||
| 	if ignoreInput { | ||||
| 		return m, cmd | ||||
| 	} | ||||
| 	cmds := []tea.Cmd{} | ||||
| 
 | ||||
| 	m.textInput, cmd = m.textInput.Update(msg) | ||||
|     cmds = append(cmds, cmd) | ||||
| 	cmds = append(cmds, cmd) | ||||
| 
 | ||||
| 
 | ||||
|     if !m.list.SettingFilter() { | ||||
|         m.list, cmd = m.list.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("/")}) | ||||
|         cmds = append(cmds, cmd) | ||||
|     } | ||||
| 	if !m.list.SettingFilter() { | ||||
| 		m.list, cmd = m.list.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("/")}) | ||||
| 		cmds = append(cmds, cmd) | ||||
| 	} | ||||
| 	m.list, cmd = m.list.Update(msg) | ||||
|     cmds = append(cmds, cmd) | ||||
| 	cmds = append(cmds, cmd) | ||||
| 
 | ||||
| 	return m, tea.Batch(cmds...) | ||||
| } | ||||
| 
 | ||||
| func (m model) View() string { | ||||
| 	if m.choice != "" { | ||||
| 		return  m.choice | ||||
| 		return m.choice | ||||
| 	} | ||||
| 	if m.quitting { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return fmt.Sprintf("%s\n", | ||||
|         m.list.View(), | ||||
| 		m.list.View(), | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| type ( | ||||
| 	errMsg error | ||||
| ) | ||||
| 
 | ||||
| type model struct { | ||||
| 	textInput textinput.Model | ||||
| 	list     list.Model | ||||
| 	choice   string | ||||
| 	quitting bool | ||||
| 	list      list.Model | ||||
| 	choice    string | ||||
| 	quitting  bool | ||||
| 	err       error | ||||
| } | ||||
| 
 | ||||
|  | @ -122,23 +121,23 @@ func initialModel(items []list.Item) model { | |||
| 	const defaultWidth = 20 | ||||
| 
 | ||||
| 	l := list.New(items, itemDelegate{}, defaultWidth, 14) | ||||
|     l.SetShowPagination(false) | ||||
| 	l.SetShowPagination(false) | ||||
| 	l.SetShowStatusBar(false) | ||||
|     l.SetShowHelp(false) | ||||
|     l.SetShowTitle(false) | ||||
| 	l.SetShowHelp(false) | ||||
| 	l.SetShowTitle(false) | ||||
| 	l.SetFilteringEnabled(true) | ||||
|     l.SetShowFilter(true) | ||||
|     l.Styles.TitleBar = lipgloss.NewStyle() | ||||
| 	l.SetShowFilter(true) | ||||
| 	l.Styles.TitleBar = lipgloss.NewStyle() | ||||
| 	ti := textinput.New() | ||||
| 	ti.Focus() | ||||
| 	ti.CharLimit = 4096 | ||||
| 	ti.Width = 20 | ||||
|     l.FilterInput = ti | ||||
|     l.Filter = Filter | ||||
| 	l.FilterInput = ti | ||||
| 	l.Filter = Filter | ||||
| 
 | ||||
| 	return model{ | ||||
|         list:     l, | ||||
| 		err:       nil, | ||||
| 		list: l, | ||||
| 		err:  nil, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue