Skip to content

Commit

Permalink
Merge pull request #113 from mistakenelf/feature/reborn
Browse files Browse the repository at this point in the history
Feature/reborn
  • Loading branch information
mistakenelf committed Mar 26, 2024
2 parents 783d1da + 890b5ac commit 19df0ed
Show file tree
Hide file tree
Showing 31 changed files with 3,418 additions and 731 deletions.
4 changes: 0 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ linters:
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- errcheck
- exportloopref
Expand All @@ -24,13 +22,11 @@ linters:
- nolintlint
- rowserrcheck
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace
- wastedassign
- nilerr
Expand Down
52 changes: 14 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ A terminal based file manager
- [Glamour](https://github.com/charmbracelet/glamour)
- [Chroma](https://github.com/alecthomas/chroma)
- [Cobra](https://github.com/spf13/cobra)
- [Teacup](https://github.com/mistakenelf/teacup)

## Installation

Expand Down Expand Up @@ -62,8 +61,7 @@ paru -S fm-bin

## Features

- Double pane layout
- File icons
- File icons (requires nerd font)
- Layout adjusts to terminal resize
- Syntax highlighting for source code with customizable themes using styles from [chroma](https://swapoff.org/chroma/playground/) (dracula, monokai etc.)
- Render pretty markdown
Expand Down Expand Up @@ -95,26 +93,32 @@ paru -S fm-bin
- `fm update` will update fm to the latest version
- `fm --start-dir=/some/start/dir` will start fm in the specified directory
- `fm --selection-path=/tmp/tmpfile` will write the selected items path to the selection path when pressing <kbd>E</kbd> and exit fm
- `fm --start-dir=/some/dir` start fm at a specific directory
- `fm --enable-logging=true` start fm with logging enabled
- `fm --pretty-markdown=true` render markdown using glamour to make it look nice
- `fm --theme=default` set the theme of fm
- `fm --show-icons=false` set whether to show icons or not
- `fm --syntax-theme=dracula` sets the syntax theme to render code with

## Navigation

| Key | Description |
| --------------------- | ---------------------------------------------------------- |
| <kbd>h or left</kbd> | Paginate to the left |
| <kbd>j or down</kbd> | Move down in the file tree or scroll pane down |
| <kbd>h or left</kbd> | Go to previous directory |
| <kbd>j or down</kbd> | Move down in the file tree or scroll pane down |
| <kbd>k or up</kbd> | Move up in the file tree or scroll pane up |
| <kbd>l or right</kbd> | Paginate to the right |
| <kbd>l or right</kbd> | Open file or directory |
| <kbd>G</kbd> | Jump to bottom of file tree or pane |
| <kbd>g</kbd> | Jump to top of file tree or pane |
| <kbd>~</kbd> | Go to home directory |
| <kbd>R</kbd> | Go to the root directory |
| <kbd>/</kbd> | Go to the root directory |
| <kbd>.</kbd> | Toggle hidden files and directories |
| <kbd>ctrl+c</kbd> | Exit |
| <kbd>q</kbd> | Exit if command bar is not open |
| <kbd>tab</kbd> | Toggle between panes |
| <kbd>esc</kbd> | Blur filetree input |
| <kbd>z</kbd> | Create a zip file of the currently selected directory item |
| <kbd>u</kbd> | Unzip a zip file |
| <kbd>esc</kbd> | Reset app state and show help screen |
| <kbd>Z</kbd> | Create a zip file of the currently selected directory item |
| <kbd>U</kbd> | Unzip a zip file |
| <kbd>c</kbd> | Create a copy of a file or directory |
| <kbd>x</kbd> | Delete the currently selected file or directory |
| <kbd>n</kbd> | Create a new file in the current directory |
Expand All @@ -127,30 +131,6 @@ paru -S fm-bin
| <kbd>?</kbd> | Toggle filetree full help menu |
| <kbd>ctrl+r</kbd> | Reload config |

## Configuration

A config file will be generated when you first run `fm`. Depending on your operating system it can be found in one of the following locations:

- macOS: ~/Library/Application\ Support/fm/config.yml
- Linux: ~/.config/fm/config.yml
- Windows: C:\Users\me\AppData\Roaming\fm\config.yml

It will include the following default settings:

```yml
settings:
borderless: false
enable_logging: false
pretty_markdown: true
show_icons: true
start_dir: .
theme:
app_theme: default
syntax_theme:
dark: dracula
light: pygments
```

## Local Development

Follow the instructions below to get setup for local development
Expand All @@ -172,7 +152,3 @@ make
```sh
make build
```

## Credit

- Thank you to this repo https://github.com/Yash-Handa/logo-ls for the icons
61 changes: 45 additions & 16 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ import (
"log"
"os"

"github.com/mistakenelf/fm/internal/config"
"github.com/mistakenelf/fm/internal/tui"

tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"

"github.com/mistakenelf/fm/filesystem"
"github.com/mistakenelf/fm/internal/theme"
"github.com/mistakenelf/fm/internal/tui"
)

var rootCmd = &cobra.Command{
Use: "fm",
Short: "FM is a simple, configurable, and fun to use file manager",
Version: "0.16.0",
Version: "1.0.0",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
startDir, err := cmd.Flags().GetString("start-dir")
Expand All @@ -28,13 +29,33 @@ var rootCmd = &cobra.Command{
log.Fatal(err)
}

cfg, err := config.ParseConfig()
enableLogging, err := cmd.Flags().GetBool("enable-logging")
if err != nil {
log.Fatal(err)
}

prettyMarkdown, err := cmd.Flags().GetBool("pretty-markdown")
if err != nil {
log.Fatal(err)
}

applicationTheme, err := cmd.Flags().GetString("theme")
if err != nil {
log.Fatal(err)
}

showIcons, err := cmd.Flags().GetBool("show-icons")
if err != nil {
log.Fatal(err)
}

syntaxTheme, err := cmd.Flags().GetString("syntax-theme")
if err != nil {
log.Fatal(err)
}

// If logging is enabled, logs will be output to debug.log.
if cfg.Settings.EnableLogging {
if enableLogging {
f, err := tea.LogToFile("debug.log", "debug")
if err != nil {
log.Fatal(err)
Expand All @@ -47,18 +68,21 @@ var rootCmd = &cobra.Command{
}()
}

if startDir == "" {
startDir = cfg.Settings.StartDir
}
appTheme := theme.GetTheme(applicationTheme)

m := tui.New(startDir, selectionPath)
var opts []tea.ProgramOption
cfg := tui.Config{
StartDir: startDir,
SelectionPath: selectionPath,
EnableLogging: enableLogging,
PrettyMarkdown: prettyMarkdown,
Theme: appTheme,
ShowIcons: showIcons,
SyntaxTheme: syntaxTheme,
}

// Always append alt screen program option.
opts = append(opts, tea.WithAltScreen())
m := tui.New(cfg)

// Initialize and start app.
p := tea.NewProgram(m, opts...)
p := tea.NewProgram(m, tea.WithAltScreen())
if _, err := p.Run(); err != nil {
log.Fatal("Failed to start fm", err)
os.Exit(1)
Expand All @@ -70,7 +94,12 @@ var rootCmd = &cobra.Command{
func Execute() {
rootCmd.AddCommand(updateCmd)
rootCmd.PersistentFlags().String("selection-path", "", "Path to write to file on open.")
rootCmd.PersistentFlags().String("start-dir", "", "Starting directory for FM")
rootCmd.PersistentFlags().String("start-dir", filesystem.CurrentDirectory, "Starting directory for FM")
rootCmd.PersistentFlags().Bool("enable-logging", false, "Enable logging for FM")
rootCmd.PersistentFlags().Bool("pretty-markdown", true, "Render markdown to look nice")
rootCmd.PersistentFlags().String("theme", "default", "Application theme")
rootCmd.PersistentFlags().Bool("show-icons", true, "Show icons")
rootCmd.PersistentFlags().String("syntax-theme", "dracula", "Set syntax theme for file output")

if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
Expand Down
169 changes: 169 additions & 0 deletions code/code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Package code implements a code bubble which renders syntax highlighted
// source code based on a filename.
package code

import (
"bytes"
"fmt"
"path/filepath"
"time"

"github.com/alecthomas/chroma/v2/quick"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"

"github.com/mistakenelf/fm/filesystem"
"github.com/mistakenelf/fm/polish"
)

type syntaxMsg string
type statusMessageTimeoutMsg struct{}
type errorMsg string

// Model represents the properties of a code bubble.
type Model struct {
Viewport viewport.Model
Filename string
Content string
SyntaxTheme string
StatusMessage string
StatusMessageLifetime time.Duration
statusMessageTimer *time.Timer
ViewportDisabled bool
}

// Highlight returns a syntax highlighted string of text.
func Highlight(content, extension, syntaxTheme string) (string, error) {
buf := new(bytes.Buffer)
if err := quick.Highlight(buf, content, extension, "terminal256", syntaxTheme); err != nil {
return "", fmt.Errorf("%w", err)
}

return buf.String(), nil
}

func readFileContentCmd(fileName, syntaxTheme string) tea.Cmd {
return func() tea.Msg {
content, err := filesystem.ReadFileContent(fileName)
if err != nil {
return errorMsg(err.Error())
}

highlightedContent, err := Highlight(content, filepath.Ext(fileName), syntaxTheme)
if err != nil {
return errorMsg(err.Error())
}

return syntaxMsg(highlightedContent)
}
}

// NewStatusMessage sets a new status message, which will show for a limited
// amount of time.
func (m *Model) NewStatusMessageCmd(s string) tea.Cmd {
m.StatusMessage = s
if m.statusMessageTimer != nil {
m.statusMessageTimer.Stop()
}

m.statusMessageTimer = time.NewTimer(m.StatusMessageLifetime)

// Wait for timeout
return func() tea.Msg {
<-m.statusMessageTimer.C
return statusMessageTimeoutMsg{}
}
}

// SetFileName sets current file to highlight.
func (m *Model) SetFileNameCmd(filename string) tea.Cmd {
m.Filename = filename

return readFileContentCmd(filename, m.SyntaxTheme)
}

// New creates a new instance of code.
func New() Model {
viewPort := viewport.New(0, 0)

return Model{
Viewport: viewPort,
SyntaxTheme: "dracula",
StatusMessage: "",
StatusMessageLifetime: time.Second,
}
}

// Init initializes the code bubble.
func (m Model) Init() tea.Cmd {
return nil
}

// SetSyntaxTheme sets the syntax theme of the rendered code.
func (m *Model) SetSyntaxTheme(theme string) {
m.SyntaxTheme = theme
}

// SetSize sets the size of the bubble.
func (m *Model) SetSize(w, h int) {
m.Viewport.Width = w
m.Viewport.Height = h
}

// GotoTop jumps to the top of the viewport.
func (m *Model) GotoTop() {
m.Viewport.GotoTop()
}

// GotoBottom jumps to the bottom of the viewport.
func (m *Model) GotoBottom() {
m.Viewport.GotoBottom()
}

// SetViewportDisabled toggles the state of the viewport.
func (m *Model) SetViewportDisabled(disabled bool) {
m.ViewportDisabled = disabled
}

// Update handles updating the UI of the code bubble.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)

switch msg := msg.(type) {
case syntaxMsg:
m.Filename = ""
m.Content = lipgloss.NewStyle().
Width(m.Viewport.Width).
Height(m.Viewport.Height).
Render(string(msg))

m.Viewport.SetContent(m.Content)

case statusMessageTimeoutMsg:
m.StatusMessage = ""
case errorMsg:
m.Filename = ""
cmds = append(cmds, m.NewStatusMessageCmd(
lipgloss.NewStyle().
Foreground(polish.Colors.Red600).
Bold(true).
Render(string(msg)),
))
}

if !m.ViewportDisabled {
m.Viewport, cmd = m.Viewport.Update(msg)
cmds = append(cmds, cmd)
}

return m, tea.Batch(cmds...)
}

// View returns a string representation of the code bubble.
func (m Model) View() string {
return m.Viewport.View()
}

0 comments on commit 19df0ed

Please sign in to comment.