package fields import ( "bubble/setup/ui" "fmt" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/sirupsen/logrus" "os" "path/filepath" "strings" ) type DirSelectModel struct { input textinput.Model label string focus int open bool dir string currentDirectory string options []os.FileInfo optionsCount int } func NewDirSelectModel(label, dir string) *DirSelectModel { inputModel := textinput.New() inputModel.SetValue(dir) return &DirSelectModel{ input: inputModel, label: label, currentDirectory: filepath.Dir(dir), open: false, dir: dir, optionsCount: 3, } } func (m *DirSelectModel) Init() tea.Cmd { return textinput.Blink } func (m *DirSelectModel) Update(msg tea.Msg) (FieldModel, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { case tea.KeyMsg: if m.open { switch msg.String() { case "esc": m.open = false case "up": m.focus-- if m.focus < 0 { m.focus = len(m.options) - 1 } case "down": m.focus++ if m.focus == len(m.options) { m.focus = 0 } case "enter": if len(m.options) == 0 || m.focus == len(m.options) { return m, nil } file := m.options[m.focus] fileName := file.Name() if fileName == ".." { m.currentDirectory = filepath.Dir(m.currentDirectory) m.focus = 0 m.RenderOptions() newValue := m.currentDirectory + string(filepath.Separator) m.input.SetValue(newValue) m.dir = newValue m.input.CursorEnd() return m, nil } if file.IsDir() { m.currentDirectory = filepath.Join(m.currentDirectory, fileName) newValue := m.currentDirectory + string(filepath.Separator) m.input.SetValue(newValue) m.dir = newValue m.input.CursorEnd() m.open = false m.focus = 0 return m, nil } } } else { switch msg.String() { case "enter": m.open = true newDir := strings.TrimSpace(m.input.Value()) if len(newDir) > 1 && strings.HasSuffix(newDir, string(filepath.Separator)) { newDir = strings.TrimSuffix(newDir, string(filepath.Separator)) } if info, err := os.Stat(newDir); err == nil && info.IsDir() { m.currentDirectory = newDir m.dir = newDir + string(filepath.Separator) m.input.SetValue(m.dir) m.input.CursorEnd() } else { m.currentDirectory = filepath.Dir(newDir) } m.RenderOptions() return m, nil default: var cmd tea.Cmd m.input, cmd = m.input.Update(msg) cmds = append(cmds, cmd) if m.input.Value() != m.dir { m.dir = m.input.Value() } } } default: if !m.open { var cmd tea.Cmd m.input, cmd = m.input.Update(msg) cmds = append(cmds, cmd) } } return m, tea.Batch(cmds...) } func (m *DirSelectModel) View() string { res := fmt.Sprintf("%s: %s", m.label, m.input.View()) if m.open && len(m.options) > 0 { visibleCount := m.optionsCount if visibleCount > len(m.options) { visibleCount = len(m.options) } half := visibleCount / 2 start := m.focus - half if start < 0 { start = 0 } end := start + visibleCount if end > len(m.options) { end = len(m.options) start = end - visibleCount if start < 0 { start = 0 } } for i := start; i < end; i++ { file := m.options[i] name := file.Name() prefix := "\n\t" if m.focus == i { prefix += "-> " } else { prefix += " " } if name == ".." { res += prefix + ui.SelectFileDirParentStyle.Render(name) } else if file.IsDir() { res += prefix + ui.SelectDirStyle.Render(name) } else { res += prefix + ui.SelectFileDirDeniedStyle.Render(name) } } } return res } func (m *DirSelectModel) Focus() { m.input.Focus() } func (m *DirSelectModel) Blur() { m.input.Blur() } func (m *DirSelectModel) CaptureKeys() bool { return m.open } func (m *DirSelectModel) GetLabel() string { return m.label } func (m *DirSelectModel) GetValue() interface{} { return m.dir } func (m *DirSelectModel) RenderOptions() { filesInDir, err := os.ReadDir(m.currentDirectory) if err != nil { logrus.Errorf("Ошибка чтения директории: %v", err) return } m.options = nil var allOptions []os.FileInfo if m.currentDirectory != "/" && filepath.Clean(m.currentDirectory) != "/" { allOptions = append(allOptions, virtualDirInfo{name: ".."}) } for _, entry := range filesInDir { info, _ := entry.Info() allOptions = append(allOptions, info) } filter := "" if m.open { inputValue := strings.TrimSpace(m.input.Value()) if inputValue != "" { lastPart := inputValue[strings.LastIndex(inputValue, "/")+1:] if lastPart != "" { filter = strings.ToLower(lastPart) } } } m.options = m.options[:0] for _, opt := range allOptions { name := opt.Name() if filter == "" || strings.HasPrefix(strings.ToLower(name), filter) || name == ".." { m.options = append(m.options, opt) } } if len(m.options) == 0 { m.focus = 0 return } if m.focus >= len(m.options) { m.focus = len(m.options) - 1 } if m.focus < 0 { m.focus = 0 } }