github.com/grzegorz-zur/bm@v0.0.0-20240312214136-6fc133e3e2c0/switch.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "sort" 8 ) 9 10 // Switch is a mode for switching files. 11 type Switch struct { 12 editor *Editor 13 query string 14 paths Paths 15 selection Paths 16 area Area 17 position Position 18 } 19 20 // Show updates mode when switched to. 21 func (mode *Switch) Show() (err error) { 22 mode.query = "" 23 mode.paths, err = mode.read() 24 if err != nil { 25 return fmt.Errorf("error showing switch mode: %w", err) 26 } 27 sort.Sort(mode.paths) 28 mode.filter() 29 return nil 30 } 31 32 // Hide updates mode when switched from. 33 func (mode *Switch) Hide() error { 34 return nil 35 } 36 37 // Key handles input events. 38 func (mode *Switch) Key(key Key) error { 39 var err error 40 switch key { 41 case KeyTab: 42 mode.editor.SwitchMode(mode.editor.Command) 43 case KeyUp: 44 mode.moveUp() 45 case KeyDown: 46 mode.moveDown() 47 case KeyLeft: 48 mode.moveLeft() 49 case KeyRight: 50 mode.moveRight() 51 case KeyBackspace: 52 mode.backspace() 53 mode.filter() 54 case KeyEnter: 55 err = mode.open() 56 mode.editor.SwitchMode(mode.editor.Command) 57 } 58 if err != nil { 59 return fmt.Errorf("error handling key %v: %w", key, err) 60 } 61 return nil 62 } 63 64 // Rune handles rune input. 65 func (mode *Switch) Rune(rune rune) error { 66 mode.append(rune) 67 mode.filter() 68 return nil 69 } 70 71 // Render renders mode. 72 func (mode *Switch) Render(view *View) error { 73 mode.area = mode.area.Resize(view.Size).Shift(mode.position) 74 selection := len(mode.selection) > 0 75 for line := mode.area.Top; line < mode.area.Bottom; line++ { 76 rline := line - mode.area.Top 77 for column := mode.area.Left; column < mode.area.Right; column++ { 78 rcolumn := column - mode.area.Left 79 if line < len(mode.selection) { 80 selected := []rune(mode.selection[line]) 81 if column < len(selected) { 82 view.Content[rline][rcolumn] = selected[column] 83 } 84 } 85 if selection && line == mode.position.Line { 86 view.Selection[rline][rcolumn] = true 87 } 88 } 89 } 90 status, err := os.Getwd() 91 if err != nil { 92 return fmt.Errorf("error getting working directory: %w", err) 93 } 94 view.Color = ColorBlue 95 view.Position = mode.position 96 view.Status = status 97 view.Prompt = string(mode.query) 98 view.Cursor = CursorPrompt 99 return nil 100 } 101 102 func (mode *Switch) filter() { 103 query := mode.query 104 mode.selection = make([]string, 0, len(mode.paths)) 105 for _, path := range mode.paths { 106 if mode.match(path, query) { 107 mode.selection = append(mode.selection, path) 108 } 109 } 110 mode.position = Position{} 111 return 112 } 113 114 func (mode *Switch) open() error { 115 position := mode.position 116 path := mode.query 117 if position.Line < len(mode.selection) { 118 path = mode.selection[position.Line] 119 } 120 err := mode.editor.Open(path) 121 if err != nil { 122 return fmt.Errorf("error opening file %s: %w", path, err) 123 } 124 return nil 125 } 126 127 func (mode *Switch) append(rune rune) { 128 mode.query = mode.query + string(rune) 129 } 130 131 func (mode *Switch) backspace() { 132 length := len(mode.query) 133 if length != 0 { 134 mode.query = mode.query[:length-1] 135 } 136 } 137 138 func (mode *Switch) moveUp() { 139 if mode.position.Line > 0 { 140 mode.position.Line-- 141 } 142 } 143 144 func (mode *Switch) moveDown() { 145 if mode.position.Line+1 < len(mode.selection) { 146 mode.position.Line++ 147 } 148 } 149 150 func (mode *Switch) moveLeft() { 151 if mode.area.Left > 0 { 152 mode.position.Column = mode.area.Left - 1 153 } else { 154 mode.position.Column = 0 155 } 156 } 157 158 func (mode *Switch) moveRight() { 159 mode.position.Column = mode.area.Right + 1 160 } 161 162 func (mode *Switch) read() (paths []string, err error) { 163 work, err := os.Getwd() 164 if err != nil { 165 return paths, fmt.Errorf("error reading working directory: %w", err) 166 } 167 walker := func(path string, info os.FileInfo, err error) error { 168 relpath, err := filepath.Rel(work, path) 169 if err != nil { 170 return fmt.Errorf("error finding relative path %s in %s: %w", path, work, err) 171 } 172 if info == nil { 173 return nil 174 } 175 if info.Mode().IsRegular() { 176 paths = append(paths, relpath) 177 } 178 return nil 179 } 180 err = filepath.Walk(work, walker) 181 if err != nil { 182 return paths, fmt.Errorf("error walking directory %s: %w", work, err) 183 } 184 return paths, nil 185 } 186 187 func (mode *Switch) match(path, query string) bool { 188 if len(query) == 0 { 189 return true 190 } 191 j := 0 192 runes := []rune(query) 193 for _, p := range path { 194 q := runes[j] 195 if p == q { 196 j++ 197 } 198 if j == len(query) { 199 return true 200 } 201 } 202 return false 203 }