├── .aliases ├── .config ├── fish │ ├── conf.d │ │ ├── my.fish │ │ ├── my.osx.fish │ │ └── my.termux.fish │ └── functions │ │ ├── brew_activate.fish │ │ └── fish_prompt.fish └── micro │ ├── bindings.chromeos.json │ ├── bindings.json │ ├── plug │ ├── filemanager │ │ ├── .editorconfig │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example.jpg │ │ ├── filemanager.lua │ │ ├── repo.json │ │ └── syntax.yaml │ └── manipulator │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── demo.gif │ │ ├── demo.txt │ │ ├── help │ │ └── manipulator.md │ │ ├── manipulator.lua │ │ └── repo.json │ └── settings.json ├── .gemrc ├── .gitconfig ├── .gitconfig.local.template ├── .gitignore ├── .inputrc ├── .screenrc ├── .termux └── termux.properties ├── .tilix.dconf ├── .tmux.conf ├── .vim └── syntax │ └── txt2tags.vim ├── .vimrc ├── README.md ├── bin ├── git-check-author ├── git-cleanup ├── git-pre-commit ├── git-pull-forced └── git-replace-text └── setup /.aliases: -------------------------------------------------------------------------------- 1 | # Aliases used by Bash and Fish (same syntax) 2 | 3 | # Lazy 4 | alias ..="cd .." 5 | alias ...="cd ../.." 6 | alias ....="cd ../../.." 7 | alias .....="cd ../../../.." 8 | alias l='ls -la' 9 | 10 | # Colors ON 11 | alias ls='ls --color=auto' 12 | alias grep='grep --color' # in Termux: pkg install grep 13 | 14 | # Docker 15 | alias docker-gc='docker system prune' 16 | 17 | # JSON 18 | alias json-pp='python -m json.tool' 19 | 20 | # Tmux: have a different session for each path 21 | alias temu='tmux new -A -D -s $PWD' 22 | 23 | # Web 24 | # curl -L https://raw.github.com/aureliojargas/css-grep/master/css-grep.txt > ~/.css.txt 25 | alias css='cat ~/.css.txt | grep -i' 26 | 27 | # VS Code 28 | # List of absolute paths for files in the current dir. 29 | # Use it from VS Code Terminal and click a path to open it in the editor. 30 | alias code-files='find "$PWD" -type f | grep -Fv /.git/' 31 | 32 | # Fish 33 | alias fish-my-config='find ~/.config/fish/ -not -type d' 34 | 35 | # Git aliases 36 | alias gg='git grep --line-number' 37 | alias ggf='git grep-filename' 38 | alias gs='git status' 39 | alias gw='git show' 40 | alias gws='git show --stat' 41 | alias gsl='git stash list' 42 | alias gsd='git stash show -p stash@{0}' 43 | alias gd='git diff' 44 | alias gdw='GIT_PAGER= git diff' # wrap long lines 45 | alias gdc='git diff --cached' 46 | alias ga='git add' 47 | alias gau='git add -u' 48 | alias gap='git add -p' 49 | alias grp='git reset -p' 50 | alias gc='git commit' 51 | alias gca='git-pre-commit; git commit --amend' 52 | alias gcaa='git commit --amend --no-verify --no-edit' 53 | alias gcm='git commit-message' 54 | alias gpf='git push --force-with-lease --force-if-includes' # safer force-push 55 | alias gpu='git push -u' 56 | alias gb='git branch' 57 | alias gba='git branch -va' 58 | alias gr='git remote -v' 59 | alias gl='git log4' 60 | alias gl2='git log2' 61 | alias gl3='git log3' 62 | 63 | # ed: Show a ruler as reference to git commit 50/72 max width 64 | alias ed-ruler="echo '-----------------------------------------------50|-------------------72|'" 65 | 66 | # vim: filetype=sh 67 | -------------------------------------------------------------------------------- /.config/fish/conf.d/my.fish: -------------------------------------------------------------------------------- 1 | # Activate brew tools (if available) 2 | brew_activate 3 | 4 | # Python tools installed with pipx 5 | fish_add_path top ~/.local/bin 6 | 7 | # Add my dear ~/bin to PATH 8 | fish_add_path ~/bin 9 | 10 | # https://github.com/fish-shell/fish-shell/issues/5394 11 | if status is-interactive 12 | 13 | # GNU ls: yellow folders, not blue 14 | set -x LS_COLORS 'di=33' 15 | 16 | # Dear Python venv, please leave my prompt alone 17 | # https://github.com/pypa/virtualenv/blob/adcf327/src/virtualenv/activation/fish/activate.fish#L80 18 | set -x VIRTUAL_ENV_DISABLE_PROMPT 1 19 | 20 | # Load aliases 21 | test -r ~/.aliases 22 | and source ~/.aliases 23 | 24 | # Colors ON 25 | # Use functions, not aliases: https://github.com/fish-shell/fish-shell/issues/6899 26 | functions --erase ls grep # remove aliases 27 | function ls 28 | command ls --color=auto $argv 29 | end 30 | function grep 31 | command grep --color $argv 32 | end 33 | 34 | # I always forget those commands 35 | function ssh-no-password 36 | eval (ssh-agent -c) 37 | ssh-add ~/.ssh/id_rsa 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /.config/fish/conf.d/my.osx.fish: -------------------------------------------------------------------------------- 1 | # Use brew GNU utils (gsed, gcut, gwc, ...) without the g prefix 2 | fish_add_path /opt/homebrew/opt/coreutils/libexec/gnubin 3 | 4 | # Use brew ruby by default (Ruby 2.7 for Jekyll 4.1.1) 5 | # fish_add_path /usr/local/opt/ruby/bin 6 | -------------------------------------------------------------------------------- /.config/fish/conf.d/my.termux.fish: -------------------------------------------------------------------------------- 1 | # Termux is a Linux emulator for Android 2 | # https://termux.com 3 | 4 | # https://github.com/fish-shell/fish-shell/issues/5394 5 | if status is-interactive 6 | 7 | # Requirement: https://wiki.termux.com/wiki/Termux:API 8 | alias pbcopy=termux-clipboard-set 9 | alias pbpaste=termux-clipboard-get 10 | 11 | # Default cursor is a non-blinking block. Change it to | 12 | # https://www.reddit.com/r/termux/comments/d9rxeo/how_do_i_change_cursor/ 13 | printf '\e[6 q' 14 | 15 | # `pkg install micro` is broken for me 16 | # Since I set micro as the git editor, map it to Vim (aliases won't work) 17 | ln -s (command -v vim) ~/bin/micro 18 | end 19 | -------------------------------------------------------------------------------- /.config/fish/functions/brew_activate.fish: -------------------------------------------------------------------------------- 1 | function brew_activate --description 'Activate brew environment (if brew is available)' 2 | 3 | # brew is already setup, nothing to do 4 | set -q HOMEBREW_REPOSITORY; and return 0 5 | 6 | # Possible paths for the brew command (Mac, Linux) 7 | set -l brew_paths \ 8 | /opt/homebrew/bin/brew \ 9 | /home/linuxbrew/.linuxbrew/bin/brew 10 | 11 | # Activate the first path found (if any) 12 | for brew in $brew_paths 13 | if command --query $brew 14 | eval ($brew shellenv) 15 | break 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /.config/fish/functions/fish_prompt.fish: -------------------------------------------------------------------------------- 1 | function fish_prompt --description 'Write out the prompt' 2 | # Quick hack to disable the fancy prompt and get a simple one 3 | # echo '$ ' 4 | # return 5 | 6 | set -l last_pipestatus $pipestatus 7 | 8 | if not set -q __fish_git_prompt_show_informative_status 9 | set -g __fish_git_prompt_show_informative_status 1 10 | end 11 | if not set -q __fish_git_prompt_hide_untrackedfiles 12 | set -g __fish_git_prompt_hide_untrackedfiles 1 13 | end 14 | if not set -q __fish_git_prompt_color_branch 15 | set -g __fish_git_prompt_color_branch magenta --bold 16 | end 17 | if not set -q __fish_git_prompt_showupstream 18 | set -g __fish_git_prompt_showupstream informative 19 | end 20 | if not set -q __fish_git_prompt_char_upstream_ahead 21 | set -g __fish_git_prompt_char_upstream_ahead "↑" 22 | end 23 | if not set -q __fish_git_prompt_char_upstream_behind 24 | set -g __fish_git_prompt_char_upstream_behind "↓" 25 | end 26 | if not set -q __fish_git_prompt_char_upstream_prefix 27 | set -g __fish_git_prompt_char_upstream_prefix "" 28 | end 29 | if not set -q __fish_git_prompt_char_stagedstate 30 | set -g __fish_git_prompt_char_stagedstate "●" 31 | end 32 | if not set -q __fish_git_prompt_char_dirtystate 33 | set -g __fish_git_prompt_char_dirtystate "✚" 34 | end 35 | if not set -q __fish_git_prompt_char_untrackedfiles 36 | set -g __fish_git_prompt_char_untrackedfiles "…" 37 | end 38 | if not set -q __fish_git_prompt_char_invalidstate 39 | set -g __fish_git_prompt_char_invalidstate "✖" 40 | end 41 | if not set -q __fish_git_prompt_char_cleanstate 42 | set -g __fish_git_prompt_char_cleanstate "✔" 43 | end 44 | if not set -q __fish_git_prompt_color_dirtystate 45 | set -g __fish_git_prompt_color_dirtystate blue 46 | end 47 | if not set -q __fish_git_prompt_color_stagedstate 48 | set -g __fish_git_prompt_color_stagedstate yellow 49 | end 50 | if not set -q __fish_git_prompt_color_invalidstate 51 | set -g __fish_git_prompt_color_invalidstate red 52 | end 53 | if not set -q __fish_git_prompt_color_untrackedfiles 54 | set -g __fish_git_prompt_color_untrackedfiles $fish_color_normal 55 | end 56 | if not set -q __fish_git_prompt_color_cleanstate 57 | set -g __fish_git_prompt_color_cleanstate green --bold 58 | end 59 | 60 | set -l color_cwd 61 | set -l prefix 62 | set -l suffix 63 | switch "$USER" 64 | case root toor 65 | if set -q fish_color_cwd_root 66 | set color_cwd $fish_color_cwd_root 67 | else 68 | set color_cwd $fish_color_cwd 69 | end 70 | set suffix '#' 71 | case '*' 72 | set color_cwd $fish_color_cwd 73 | set suffix '$' 74 | end 75 | 76 | # horizontal line 77 | set_color green 78 | string repeat -n $COLUMNS – 79 | set_color normal 80 | 81 | # show a red "ssh" if we're on a remote machine 82 | if set -q SSH_TTY; or set -q SSH_CLIENT 83 | set_color red 84 | echo -n 'ssh ' 85 | set_color normal 86 | end 87 | 88 | # working dir 89 | set_color green 90 | echo -n (prompt_pwd) 91 | set_color normal 92 | 93 | # git 94 | printf '%s ' (fish_vcs_prompt) 95 | 96 | # show activated Python virtual env 97 | if set -q VIRTUAL_ENV 98 | set_color yellow 99 | printf '[%s] ' (basename $VIRTUAL_ENV) 100 | set_color normal 101 | end 102 | 103 | # exit status from last command (e.g.: "[1]" in red) 104 | set -l pipestatus_string (__fish_print_pipestatus "[" "] " "|" (set_color $fish_color_status) (set_color --bold $fish_color_status) $last_pipestatus) 105 | echo -n $pipestatus_string 106 | set_color normal 107 | 108 | # line break 109 | echo 110 | 111 | # finally, the actual "$ " prompt 112 | echo -n "$suffix " 113 | end 114 | -------------------------------------------------------------------------------- /.config/micro/bindings.chromeos.json: -------------------------------------------------------------------------------- 1 | { 2 | "Alt-/": "lua:comment.comment", 3 | "Alt-q": "command-edit:textfilter fmt -w 80 -p #", 4 | "CtrlUnderscore": "lua:comment.comment", 5 | "PageDown": "MoveLinesDown", 6 | "PageUp": "MoveLinesUp" 7 | } 8 | -------------------------------------------------------------------------------- /.config/micro/bindings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Alt-/": "lua:comment.comment", 3 | "Alt-q": "command-edit:textfilter fmt -w 80 -p #", 4 | "CtrlUnderscore": "lua:comment.comment", 5 | } 6 | -------------------------------------------------------------------------------- /.config/micro/plug/filemanager/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_style = tab 11 | indent_size = 2 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | # Ignore .md files, because 2 spaces at end-of-line has meaning 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.config/micro/plug/filemanager/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [3.4.0] - 2018-10-22 10 | 11 | ### Fixed 12 | 13 | - Issues with Lua's `io.popen` on some systems by using Micro's built-in `RunShellCommand` instead, [thanks to @scottbilas](https://github.com/NicolaiSoeborg/filemanager-plugin/pull/38) 14 | 15 | ### Added 16 | 17 | - Adds the option `filemanager-openonstart` to allow auto-opening the file tree when Micro is started (default OFF) 18 | 19 | ### Changed 20 | 21 | - Update README's option's documentation 22 | 23 | ## [3.3.1] - 2018-10-03 24 | 25 | ### Changed 26 | 27 | - Performance improvement by removing unnecessary refresh of the opened file, [thanks to @jackwilsdon](https://github.com/NicolaiSoeborg/filemanager-plugin/pull/37) 28 | 29 | ## [3.3.0] - 2018-09-13 30 | 31 | ### Added 32 | 33 | - The ability to sort folders above files, [thanks to @cbrown1](https://github.com/NicolaiSoeborg/filemanager-plugin/pull/33) 34 | 35 | ### Fixed 36 | 37 | - The displayed filenames are now correctly only showing their "basename" on Windows 38 | 39 | ## [3.2.0] - 2018-02-15 40 | 41 | ### Added 42 | 43 | - The ability to go to parent directory with left arrow (when not minimizing). Thanks @avently 44 | - The ability to jump to the `..` as a "parent directory". Thanks @avently 45 | 46 | ## [3.1.2] - 2018-02-07 47 | 48 | ### Fixed 49 | 50 | - The minimum Micro version, which was incorrectly set to v1.4.0. Ref [issue #28](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/28) 51 | 52 | ## [3.1.1] - 2018-02-04 53 | 54 | ### Fixed 55 | 56 | Ref https://github.com/zyedidia/micro/issues/992 for both of these fixes. 57 | 58 | - The syntax parser not loading correctly (mostly block comments) on opened files. **Requires Micro >= v1.4.0** 59 | - An errant tab being inserted into the newly opened file. 60 | 61 | ## [3.1.0] - 2018-01-30 62 | 63 | ### Added 64 | 65 | - The ability to hide dotfiles using the `filemanager-showdotfiles` option. 66 | - The ability to hide files ignored in your VCS (aka `.gitignore`'d) using the `filemanager-showignored` option. Only works with Git at the moment. 67 | - This `CHANGELOG.md` 68 | 69 | ### Fixed 70 | 71 | - A bug with the `rm` command that caused weird, undefined behaviour to contents within the same dir as the file/dir deleted. 72 | - Issue [#24](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/24) 73 | 74 | ## [3.0.0] - 2018-01-10 75 | 76 | ### Fixed 77 | 78 | - Issues [#13](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/13), [#14](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/14), [#15](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/15), [#19](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/19), [#20](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/20) 79 | - The broken syntax highlighting 80 | 81 | ### Added 82 | 83 | - Directory expansion/compression below itself for viewing more akin to a file tree. 84 | - The `rm` command, which deletes the file/directory under the cursor. 85 | - The `touch` command, which creates a file with the passed filename. 86 | - The `mkdir` command, which creates a directory with the passed filename. 87 | - An API, of sorts, for the user to rebind their keys to if they dislike the defaults. 88 | - An [editorconfig](http://editorconfig.org/) file. 89 | 90 | ### Changed 91 | 92 | - The view that it spawns in to read-only, which requires Micro version >= 1.3.5 93 | - The functionality of some keybindings (when in the view) so they work safetly, or at all, with the plugin. 94 | - From the `enter` key to `tab` for opening/going into files/dirs (a side-effect of using the read-only setting) 95 | 96 | ### Removed 97 | 98 | - The ability to use a lot of keybindings that would otherwise mess with the view, and have no benifit. 99 | - The pointless `.gitignore` file. 100 | 101 | [unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.4.0...HEAD 102 | [3.4.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.3.1...v3.4.0 103 | [3.3.1]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.3.0...v3.3.1 104 | [3.3.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.2.0...v3.3.0 105 | [3.2.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.2...v3.2.0 106 | [3.1.2]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.1...v3.1.2 107 | [3.1.1]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.0...v3.1.1 108 | [3.1.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.0.0...v3.1.0 109 | [3.0.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v2.1.1...v3.0.0 110 | -------------------------------------------------------------------------------- /.config/micro/plug/filemanager/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nicolai Søborg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.config/micro/plug/filemanager/README.md: -------------------------------------------------------------------------------- 1 | # Filemanager Plugin 2 | 3 | A simple plugin that allows for easy navigation of a file tree. 4 | 5 | ![Example picture](./example.jpg?raw=true "Example") 6 | 7 | **Installation:** run `plugin install filemanager` and restart Micro. 8 | 9 | ## Basics 10 | 11 | The top line always has the current directory's path to show you where you are.\ 12 | The `..` near the top is used to move back a directory, from your current position. 13 | 14 | All directories have a `/` added to the end of it, and are syntax-highlighted as a `special` character.\ 15 | If the directory is expanded, there will be a `+` to the left of it. If it is collapsed there will be a `-` instead. 16 | 17 | **NOTE:** If you change files without using the plugin, it can't know what you did. The only fix is to close and open the tree. 18 | 19 | ### Options 20 | 21 | | Option | Purpose | Default | 22 | | :--------------------------- | :----------------------------------------------------------- | :------ | 23 | | `filemanager-showdotfiles` | Show dotfiles (hidden if false) | `true` | 24 | | `filemanager-showignored` | Show gitignore'd files (hidden if false) | `true` | 25 | | `filemanager-compressparent` | Collapse the parent dir when left is pressed on a child file | `true` | 26 | | `filemanager-foldersfirst` | Sorts folders above any files | `true` | 27 | | `filemanager-openonstart` | Automatically open the file tree when starting Micro | `false` | 28 | 29 | ### Commands and Keybindings 30 | 31 | The keybindings below are the equivalent to Micro's defaults, and not actually set by the plugin. If you've changed any of those keybindings, then that key is used instead. 32 | 33 | If you want to [keybind](https://github.com/zyedidia/micro/blob/master/runtime/help/keybindings.md#rebinding-keys) any of the operations/commands, bind to the labeled API in the table below. 34 | 35 | | Command | Keybinding(s) | What it does | API for `bindings.json` | 36 | | :------- | :------------------------- | :------------------------------------------------------------------------------------------ | :------------------------------------ | 37 | | `tree` | - | Open/close the tree | `filemanager.toggle_tree` | 38 | | - | Tab & MouseLeft | Open a file, or go into the directory. Goes back a dir if on `..` | `filemanager.try_open_at_cursor` | 39 | | - | | Expand directory in tree listing | `filemanager.uncompress_at_cursor` | 40 | | - | | Collapse directory listing | `filemanager.compress_at_cursor` | 41 | | - | Shift ⬆ | Go to the target's parent directory | `filemanager.goto_parent_dir` | 42 | | - | Alt Shift { | Jump to the previous directory in the view | `filemanager.goto_next_dir` | 43 | | - | Alt Shift } | Jump to the next directory in the view | `filemanager.goto_prev_dir` | 44 | | `rm` | - | Prompt to delete the target file/directory your cursor is on | `filemanager.prompt_delete_at_cursor` | 45 | | `rename` | - | Rename the file/directory your cursor is on, using the passed name | `filemanager.rename_at_cursor` | 46 | | `touch` | - | Make a new file under/into the file/directory your cursor is on, using the passed name | `filemanager.new_file` | 47 | | `mkdir` | - | Make a new directory under/into the file/directory your cursor is on, using the passed name | `filemanager.new_dir` | 48 | 49 | #### Notes 50 | 51 | - `rename`, `touch`, and `mkdir` require a name to be passed when calling.\ 52 | Example: `rename newnamehere`, `touch filenamehere`, `mkdir dirnamehere`.\ 53 | If the passed name already exists in the current dir, it will cancel instead of overwriting (for safety). 54 | 55 | - The Ctrl w keybinding is to switch which buffer your cursor is on.\ 56 | This isn't specific to the plugin, it's just part of Micro, but many people seem to not know this. 57 | -------------------------------------------------------------------------------- /.config/micro/plug/filemanager/example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aureliojargas/dotfiles/f35732301f6592415f45038c00207c7e463b685e/.config/micro/plug/filemanager/example.jpg -------------------------------------------------------------------------------- /.config/micro/plug/filemanager/filemanager.lua: -------------------------------------------------------------------------------- 1 | VERSION = "3.5.1" 2 | 3 | local micro = import("micro") 4 | local config = import("micro/config") 5 | local shell = import("micro/shell") 6 | local buffer = import("micro/buffer") 7 | local os = import("os") 8 | local filepath = import("path/filepath") 9 | 10 | -- Clear out all stuff in Micro's messenger 11 | local function clear_messenger() 12 | -- messenger:Reset() 13 | -- messenger:Clear() 14 | end 15 | 16 | -- Holds the micro.CurPane() we're manipulating 17 | local tree_view = nil 18 | -- Keeps track of the current working directory 19 | local current_dir = os.Getwd() 20 | -- Keep track of current highest visible indent to resize width appropriately 21 | local highest_visible_indent = 0 22 | -- Holds a table of paths -- objects from new_listobj() calls 23 | local scanlist = {} 24 | 25 | -- Get a new object used when adding to scanlist 26 | local function new_listobj(p, d, o, i) 27 | return { 28 | ["abspath"] = p, 29 | ["dirmsg"] = d, 30 | ["owner"] = o, 31 | ["indent"] = i, 32 | -- Since decreasing/increasing is common, we include these with the object 33 | ["decrease_owner"] = function(self, minus_num) 34 | self.owner = self.owner - minus_num 35 | end, 36 | ["increase_owner"] = function(self, plus_num) 37 | self.owner = self.owner + plus_num 38 | end 39 | } 40 | end 41 | 42 | -- Repeats a string x times, then returns it concatenated into one string 43 | local function repeat_str(str, len) 44 | -- Do NOT try to concat in a loop, it freezes micro... 45 | -- instead, use a temporary table to hold values 46 | local string_table = {} 47 | for i = 1, len do 48 | string_table[i] = str 49 | end 50 | -- Return the single string of repeated characters 51 | return table.concat(string_table) 52 | end 53 | 54 | -- A check for if a path is a dir 55 | local function is_dir(path) 56 | -- Used for checking if dir 57 | local golib_os = import("os") 58 | -- Returns a FileInfo on the current file/path 59 | local file_info, stat_error = golib_os.Stat(path) 60 | -- Wrap in nil check for file/dirs without read permissions 61 | if file_info ~= nil then 62 | -- Returns true/false if it's a dir 63 | return file_info:IsDir() 64 | else 65 | -- Couldn't stat the file/dir, usually because no read permissions 66 | micro.InfoBar():Error("Error checking if is dir: ", stat_error) 67 | -- Nil since we can't read the path 68 | return nil 69 | end 70 | end 71 | 72 | -- Returns a list of files (in the target dir) that are ignored by the VCS system (if exists) 73 | -- aka this returns a list of gitignored files (but for whatever VCS is found) 74 | local function get_ignored_files(tar_dir) 75 | -- True/false if the target dir returns a non-fatal error when checked with 'git status' 76 | local function has_git() 77 | local git_rp_results = shell.ExecCommand('git -C "' .. tar_dir .. '" rev-parse --is-inside-work-tree') 78 | return git_rp_results:match("^true%s*$") 79 | end 80 | local readout_results = {} 81 | -- TODO: Support more than just Git, such as Mercurial or SVN 82 | if has_git() then 83 | -- If the dir is a git dir, get all ignored in the dir 84 | local git_ls_results = 85 | shell.ExecCommand('git -C "' .. tar_dir .. '" ls-files . --ignored --exclude-standard --others --directory') 86 | -- Cut off the newline that is at the end of each result 87 | for split_results in string.gmatch(git_ls_results, "([^\r\n]+)") do 88 | -- git ls-files adds a trailing slash if it's a dir, so we remove it (if it is one) 89 | readout_results[#readout_results + 1] = 90 | (string.sub(split_results, -1) == "/" and string.sub(split_results, 1, -2) or split_results) 91 | end 92 | end 93 | 94 | -- Make sure we return a table 95 | return readout_results 96 | end 97 | 98 | -- Returns the basename of a path (aka a name without leading path) 99 | local function get_basename(path) 100 | if path == nil then 101 | micro.Log("Bad path passed to get_basename") 102 | return nil 103 | else 104 | -- Get Go's path lib for a basename callback 105 | local golib_path = import("filepath") 106 | return golib_path.Base(path) 107 | end 108 | end 109 | 110 | -- Returns true/false if the file is a dotfile 111 | local function is_dotfile(file_name) 112 | -- Check if the filename starts with a dot 113 | if string.sub(file_name, 1, 1) == "." then 114 | return true 115 | else 116 | return false 117 | end 118 | end 119 | 120 | -- Structures the output of the scanned directory content to be used in the scanlist table 121 | -- This is useful for both initial creation of the tree, and when nesting with uncompress_target() 122 | local function get_scanlist(dir, ownership, indent_n) 123 | local golib_ioutil = import("ioutil") 124 | -- Gets a list of all the files in the current dir 125 | local dir_scan, scan_error = golib_ioutil.ReadDir(dir) 126 | 127 | -- dir_scan will be nil if the directory is read-protected (no permissions) 128 | if dir_scan == nil then 129 | micro.InfoBar():Error("Error scanning dir: ", scan_error) 130 | return nil 131 | end 132 | 133 | -- The list of files to be returned (and eventually put in the view) 134 | local results = {} 135 | local files = {} 136 | 137 | local function get_results_object(file_name) 138 | local abs_path = filepath.Join(dir, file_name) 139 | -- Use "+" for dir's, "" for files 140 | local dirmsg = (is_dir(abs_path) and "+" or "") 141 | return new_listobj(abs_path, dirmsg, ownership, indent_n) 142 | end 143 | 144 | -- Save so we don't have to rerun GetOption a bunch 145 | local show_dotfiles = config.GetGlobalOption("filemanager.showdotfiles") 146 | local show_ignored = config.GetGlobalOption("filemanager.showignored") 147 | local folders_first = config.GetGlobalOption("filemanager.foldersfirst") 148 | 149 | -- The list of VCS-ignored files (if any) 150 | -- Only bother getting ignored files if we're not showing ignored 151 | local ignored_files = (not show_ignored and get_ignored_files(dir) or {}) 152 | -- True/false if the file is an ignored file 153 | local function is_ignored_file(filename) 154 | for i = 1, #ignored_files do 155 | if ignored_files[i] == filename then 156 | return true 157 | end 158 | end 159 | return false 160 | end 161 | 162 | -- Hold the current scan's filename in most of the loops below 163 | local filename 164 | 165 | for i = 1, #dir_scan do 166 | local showfile = true 167 | filename = dir_scan[i]:Name() 168 | -- If we should not show dotfiles, and this is a dotfile, don't show 169 | if not show_dotfiles and is_dotfile(filename) then 170 | showfile = false 171 | end 172 | -- If we should not show ignored files, and this is an ignored file, don't show 173 | if not show_ignored and is_ignored_file(filename) then 174 | showfile = false 175 | end 176 | if showfile then 177 | -- This file is good to show, proceed 178 | if folders_first and not is_dir(filepath.Join(dir, filename)) then 179 | -- If folders_first and this is a file, add it to (temporary) files 180 | files[#files + 1] = get_results_object(filename) 181 | else 182 | -- Otherwise, add to results 183 | results[#results + 1] = get_results_object(filename) 184 | end 185 | end 186 | end 187 | if #files > 0 then 188 | -- Append any files to results, now that all folders have been added 189 | -- files will be > 0 only if folders_first and there are files 190 | for i = 1, #files do 191 | results[#results + 1] = files[i] 192 | end 193 | end 194 | 195 | -- Return the list of scanned files 196 | return results 197 | end 198 | 199 | -- A short "get y" for when acting on the scanlist 200 | -- Needed since we don't store the first 3 visible indicies in scanlist 201 | local function get_safe_y(optional_y) 202 | -- Default to 0 so we can check against and see if it's bad 203 | local y = 0 204 | -- Make the passed y optional 205 | if optional_y == nil then 206 | -- Default to cursor's Y loc if nothing was passed, instead of declaring another y 207 | optional_y = tree_view.Cursor.Loc.Y 208 | end 209 | -- 0/1/2 would be the top "dir, separator, .." so check if it's past 210 | if optional_y > 2 then 211 | -- -2 to conform to our scanlist, since zero-based Go index & Lua's one-based 212 | y = tree_view.Cursor.Loc.Y - 2 213 | end 214 | return y 215 | end 216 | 217 | -- Joins the target dir's leading path to the passed name 218 | local function dirname_and_join(path, join_name) 219 | -- The leading path to the dir we're in 220 | local leading_path = filepath.Dir(path) 221 | -- Joins with OS-specific slashes 222 | return filepath.Join(leading_path, join_name) 223 | end 224 | 225 | -- Hightlights the line when you move the cursor up/down 226 | local function select_line(last_y) 227 | -- Make last_y optional 228 | if last_y ~= nil then 229 | -- Don't let them move past ".." by checking the result first 230 | if last_y > 1 then 231 | -- If the last position was valid, move back to it 232 | tree_view.Cursor.Loc.Y = last_y 233 | end 234 | elseif tree_view.Cursor.Loc.Y < 2 then 235 | -- Put the cursor on the ".." if it's above it 236 | tree_view.Cursor.Loc.Y = 2 237 | end 238 | 239 | -- Puts the cursor back in bounds (if it isn't) for safety 240 | tree_view.Cursor:Relocate() 241 | 242 | -- Makes sure the cursor is visible (if it isn't) 243 | -- (false) means no callback 244 | tree_view:Center() 245 | 246 | -- Highlight the current line where the cursor is 247 | tree_view.Cursor:SelectLine() 248 | end 249 | 250 | -- Simple true/false if scanlist is currently empty 251 | local function scanlist_is_empty() 252 | if next(scanlist) == nil then 253 | return true 254 | else 255 | return false 256 | end 257 | end 258 | 259 | local function refresh_view() 260 | clear_messenger() 261 | 262 | -- If it's less than 30, just use 30 for width. Don't want it too small 263 | 264 | if tree_view:GetView().Width < 30 then 265 | tree_view:ResizePane(30) 266 | end 267 | 268 | -- Delete everything in the view/buffer 269 | tree_view.Buf.EventHandler:Remove(tree_view.Buf:Start(), tree_view.Buf:End()) 270 | 271 | -- Insert the top 3 things that are always there 272 | -- Current dir 273 | tree_view.Buf.EventHandler:Insert(buffer.Loc(0, 0), current_dir .. "\n") 274 | -- An ASCII separator 275 | tree_view.Buf.EventHandler:Insert(buffer.Loc(0, 1), repeat_str("─", tree_view:GetView().Width) .. "\n") 276 | -- The ".." and use a newline if there are things in the current dir 277 | tree_view.Buf.EventHandler:Insert(buffer.Loc(0, 2), (#scanlist > 0 and "..\n" or "..")) 278 | 279 | -- Holds the current basename of the path (purely for display) 280 | local display_content 281 | 282 | -- NOTE: might want to not do all these concats in the loop, it can get slow 283 | for i = 1, #scanlist do 284 | -- The first 3 indicies are the dir/separator/"..", so skip them 285 | if scanlist[i].dirmsg ~= "" then 286 | -- Add the + or - to the left to signify if it's compressed or not 287 | -- Add a forward slash to the right to signify it's a dir 288 | display_content = scanlist[i].dirmsg .. " " .. get_basename(scanlist[i].abspath) .. "/" 289 | else 290 | -- Use the basename from the full path for display 291 | -- Two spaces to align with any directories, instead of being "off" 292 | display_content = " " .. get_basename(scanlist[i].abspath) 293 | end 294 | 295 | if scanlist[i].owner > 0 then 296 | -- Add a space and repeat it * the indent number 297 | display_content = repeat_str(" ", 2 * scanlist[i].indent) .. display_content 298 | end 299 | 300 | -- Newlines are needed for all inserts except the last 301 | -- If you insert a newline on the last, it leaves a blank spot at the bottom 302 | if i < #scanlist then 303 | display_content = display_content .. "\n" 304 | end 305 | 306 | -- Insert line-by-line to avoid out-of-bounds on big folders 307 | -- +2 so we skip the 0/1/2 positions that hold the top dir/separator/.. 308 | tree_view.Buf.EventHandler:Insert(buffer.Loc(0, i + 2), display_content) 309 | end 310 | 311 | -- Resizes all views after messing with ours 312 | tree_view:Tab():Resize() 313 | end 314 | 315 | -- Moves the cursor to the ".." in tree_view 316 | local function move_cursor_top() 317 | -- 2 is the position of the ".." 318 | tree_view.Cursor.Loc.Y = 2 319 | 320 | -- select the line after moving 321 | select_line() 322 | end 323 | 324 | local function refresh_and_select() 325 | -- Save the cursor position before messing with the view.. 326 | -- because changing contents in the view causes the Y loc to move 327 | local last_y = tree_view.Cursor.Loc.Y 328 | -- Actually refresh 329 | refresh_view() 330 | -- Moves the cursor back to it's original position 331 | select_line(last_y) 332 | end 333 | 334 | -- Find everything nested under the target, and remove it from the scanlist 335 | local function compress_target(y, delete_y) 336 | -- Can't compress the top stuff, or if there's nothing there, so exit early 337 | if y == 0 or scanlist_is_empty() then 338 | return 339 | end 340 | -- Check if the target is a dir, since files don't have anything to compress 341 | -- Also make sure it's actually an uncompressed dir by checking the gutter message 342 | if scanlist[y].dirmsg == "-" then 343 | local target_index, delete_index 344 | -- Add the original target y to stuff to delete 345 | local delete_under = {[1] = y} 346 | local new_table = {} 347 | local del_count = 0 348 | -- Loop through the whole table, looking for nested content, or stuff with ownership == y... 349 | -- and delete matches. y+1 because we want to start under y, without actually touching y itself. 350 | for i = 1, #scanlist do 351 | delete_index = false 352 | -- Don't run on y, since we don't always delete y 353 | if i ~= y then 354 | -- On each loop, check if the ownership matches 355 | for x = 1, #delete_under do 356 | -- Check for something belonging to a thing to delete 357 | if scanlist[i].owner == delete_under[x] then 358 | -- Delete the target if it has an ownership to our delete target 359 | delete_index = true 360 | -- Keep count of total deleted (can't use #delete_under because it's for deleted dir count) 361 | del_count = del_count + 1 362 | -- Check if an uncompressed dir 363 | if scanlist[i].dirmsg == "-" then 364 | -- Add the index to stuff to delete, since it holds nested content 365 | delete_under[#delete_under + 1] = i 366 | end 367 | -- See if we're on the "deepest" nested content 368 | if scanlist[i].indent == highest_visible_indent and scanlist[i].indent > 0 then 369 | -- Save the lower indent, since we're minimizing/deleting nested dirs 370 | highest_visible_indent = highest_visible_indent - 1 371 | end 372 | -- Nothing else to do, so break this inner loop 373 | break 374 | end 375 | end 376 | end 377 | if not delete_index then 378 | -- Save the index in our new table 379 | new_table[#new_table + 1] = scanlist[i] 380 | end 381 | end 382 | 383 | scanlist = new_table 384 | 385 | if del_count > 0 then 386 | -- Ownership adjusting since we're deleting an index 387 | for i = y + 1, #scanlist do 388 | -- Don't touch root file/dirs 389 | if scanlist[i].owner > y then 390 | -- Minus ownership, on everything below i, the number deleted 391 | scanlist[i]:decrease_owner(del_count) 392 | end 393 | end 394 | end 395 | 396 | -- If not deleting, then update the gutter message to be + to signify compressed 397 | if not delete_y then 398 | -- Update the dir message 399 | scanlist[y].dirmsg = "+" 400 | end 401 | elseif config.GetGlobalOption("filemanager.compressparent") and not delete_y then 402 | goto_parent_dir() 403 | -- Prevent a pointless refresh of the view 404 | return 405 | end 406 | 407 | -- Put outside check above because we call this to delete targets as well 408 | if delete_y then 409 | local second_table = {} 410 | -- Quickly remove y 411 | for i = 1, #scanlist do 412 | if i == y then 413 | -- Reduce everything's ownership by 1 after y 414 | for x = i + 1, #scanlist do 415 | -- Don't touch root file/dirs 416 | if scanlist[x].owner > y then 417 | -- Minus 1 since we're just deleting y 418 | scanlist[x]:decrease_owner(1) 419 | end 420 | end 421 | else 422 | -- Put everything but y into the temporary table 423 | second_table[#second_table + 1] = scanlist[i] 424 | end 425 | end 426 | -- Put everything (but y) back into scanlist, with adjusted ownership values 427 | scanlist = second_table 428 | end 429 | 430 | if tree_view:GetView().Width > (30 + highest_visible_indent) then 431 | -- Shave off some width 432 | tree_view:ResizePane(30 + highest_visible_indent) 433 | end 434 | 435 | refresh_and_select() 436 | end 437 | 438 | -- Prompts the user for deletion of a file/dir when triggered 439 | -- Not local so Micro can access it 440 | function prompt_delete_at_cursor() 441 | local y = get_safe_y() 442 | -- Don't let them delete the top 3 index dir/separator/.. 443 | if y == 0 or scanlist_is_empty() then 444 | micro.InfoBar():Error("You can't delete that") 445 | -- Exit early if there's nothing to delete 446 | return 447 | end 448 | 449 | micro.InfoBar():YNPrompt("Do you want to delete the " .. (scanlist[y].dirmsg ~= "" and "dir" or "file") .. ' "' .. scanlist[y].abspath .. '"? ', function(yes, canceled) 450 | if yes and not canceled then 451 | -- Use Go's os.Remove to delete the file 452 | local go_os = import("os") 453 | -- Delete the target (if its a dir then the children too) 454 | local remove_log = go_os.RemoveAll(scanlist[y].abspath) 455 | if remove_log == nil then 456 | micro.InfoBar():Message("Filemanager deleted: ", scanlist[y].abspath) 457 | -- Remove the target (and all nested) from scanlist[y + 1] 458 | -- true to delete y 459 | compress_target(get_safe_y(), true) 460 | else 461 | micro.InfoBar():Error("Failed deleting file/dir: ", remove_log) 462 | end 463 | else 464 | micro.InfoBar():Message("Nothing was deleted") 465 | end 466 | end) 467 | end 468 | 469 | -- Changes the current dir in the top of the tree.. 470 | -- then scans that dir, and prints it to the view 471 | local function update_current_dir(path) 472 | -- Clear the highest since this is a full refresh 473 | highest_visible_indent = 0 474 | -- Set the width back to 30 475 | tree_view:ResizePane(30) 476 | -- Update the current dir to the new path 477 | current_dir = path 478 | 479 | -- Get the current working dir's files into our list of files 480 | -- 0 ownership because this is a scan of the base dir 481 | -- 0 indent because this is the base dir 482 | local scan_results = get_scanlist(path, 0, 0) 483 | -- Safety check with not-nil 484 | if scan_results ~= nil then 485 | -- Put in the new scan stuff 486 | scanlist = scan_results 487 | else 488 | -- If nil, just empty it 489 | scanlist = {} 490 | end 491 | 492 | refresh_view() 493 | -- Since we're going into a new dir, move cursor to the ".." by default 494 | move_cursor_top() 495 | end 496 | 497 | -- (Tries to) go back one "step" from the current directory 498 | local function go_back_dir() 499 | -- Use Micro's dirname to get everything but the current dir's path 500 | local one_back_dir = filepath.Dir(current_dir) 501 | -- Try opening, assuming they aren't at "root", by checking if it matches last dir 502 | if one_back_dir ~= current_dir then 503 | -- If filepath.Dir returns different, then they can move back.. 504 | -- so we update the current dir and refresh 505 | update_current_dir(one_back_dir) 506 | end 507 | end 508 | 509 | -- Tries to open the current index 510 | -- If it's the top dir indicator, or separator, nothing happens 511 | -- If it's ".." then it tries to go back a dir 512 | -- If it's a dir then it moves into the dir and refreshes 513 | -- If it's actually a file, open it in a new vsplit 514 | -- THIS EXPECTS ZERO-BASED Y 515 | local function try_open_at_y(y) 516 | -- 2 is the zero-based index of ".." 517 | if y == 2 then 518 | go_back_dir() 519 | elseif y > 2 and not scanlist_is_empty() then 520 | -- -2 to conform to our scanlist "missing" first 3 indicies 521 | y = y - 2 522 | if scanlist[y].dirmsg ~= "" then 523 | -- if passed path is a directory, update the current dir to be one deeper.. 524 | update_current_dir(scanlist[y].abspath) 525 | else 526 | -- If it's a file, then open it 527 | micro.InfoBar():Message("Filemanager opened ", scanlist[y].abspath) 528 | -- Opens the absolute path in new vertical view 529 | micro.CurPane():VSplitIndex(buffer.NewBufferFromFile(scanlist[y].abspath), true) 530 | -- Resizes all views after opening a file 531 | -- tabs[curTab + 1]:Resize() 532 | end 533 | else 534 | micro.InfoBar():Error("Can't open that") 535 | end 536 | end 537 | 538 | -- Opens the dir's contents nested under itself 539 | local function uncompress_target(y) 540 | -- Exit early if on the top 3 non-list items 541 | if y == 0 or scanlist_is_empty() then 542 | return 543 | end 544 | -- Only uncompress if it's a dir and it's not already uncompressed 545 | if scanlist[y].dirmsg == "+" then 546 | -- Get a new scanlist with results from the scan in the target dir 547 | local scan_results = get_scanlist(scanlist[y].abspath, y, scanlist[y].indent + 1) 548 | -- Don't run any of this if there's nothing in the dir we scanned, pointless 549 | if scan_results ~= nil then 550 | -- Will hold all the old values + new scan results 551 | local new_table = {} 552 | -- By not inserting in-place, some unexpected results can be avoided 553 | -- Also, table.insert actually moves values up (???) instead of down 554 | for i = 1, #scanlist do 555 | -- Put the current val into our new table 556 | new_table[#new_table + 1] = scanlist[i] 557 | if i == y then 558 | -- Fill in the scan results under y 559 | for x = 1, #scan_results do 560 | new_table[#new_table + 1] = scan_results[x] 561 | end 562 | -- Basically "moving down" everything below y, so ownership needs to increase on everything 563 | for inner_i = y + 1, #scanlist do 564 | -- When root not pushed by inserting, don't change its ownership 565 | -- This also has a dual-purpose to make it not effect root file/dirs 566 | -- since y is always >= 3 567 | if scanlist[inner_i].owner > y then 568 | -- Increase each indicies ownership by the number of scan results inserted 569 | scanlist[inner_i]:increase_owner(#scan_results) 570 | end 571 | end 572 | end 573 | end 574 | 575 | -- Update our scanlist with the new values 576 | scanlist = new_table 577 | end 578 | 579 | -- Change to minus to signify it's uncompressed 580 | scanlist[y].dirmsg = "-" 581 | 582 | -- Check if we actually need to resize, or if we're nesting at the same indent 583 | -- Also check if there's anything in the dir, as we don't need to expand on an empty dir 584 | if scan_results ~= nil then 585 | if scanlist[y].indent > highest_visible_indent and #scan_results >= 1 then 586 | -- Save the new highest indent 587 | highest_visible_indent = scanlist[y].indent 588 | -- Increase the width to fit the new nested content 589 | tree_view:ResizePane(tree_view:GetView().Width + scanlist[y].indent) 590 | end 591 | end 592 | 593 | refresh_and_select() 594 | end 595 | end 596 | 597 | -- Stat a path to check if it exists, returning true/false 598 | local function path_exists(path) 599 | local go_os = import("os") 600 | -- Stat the file/dir path we created 601 | -- file_stat should be non-nil, and stat_err should be nil on success 602 | local file_stat, stat_err = go_os.Stat(path) 603 | -- Check if what we tried to create exists 604 | if stat_err ~= nil then 605 | -- true/false if the file/dir exists 606 | return go_os.IsExist(stat_err) 607 | elseif file_stat ~= nil then 608 | -- Assume it exists if no errors 609 | return true 610 | end 611 | return false 612 | end 613 | 614 | -- Prompts for a new name, then renames the file/dir at the cursor's position 615 | -- Not local so Micro can use it 616 | function rename_at_cursor(bp, args) 617 | 618 | if micro.CurPane() ~= tree_view then 619 | micro.InfoBar():Message("Rename only works with the cursor in the tree!") 620 | return 621 | end 622 | 623 | -- Safety check they actually passed a name 624 | if #args < 1 then 625 | micro.InfoBar():Error('When using "rename" you need to input a new name') 626 | return 627 | end 628 | 629 | local new_name = args[1] 630 | 631 | -- +1 since Go uses zero-based indices 632 | local y = get_safe_y() 633 | -- Check if they're trying to rename the top stuff 634 | if y == 0 then 635 | -- Error since they tried to rename the top stuff 636 | micro.InfoBar():Message("You can't rename that!") 637 | return 638 | end 639 | 640 | -- The old file/dir's path 641 | local old_path = scanlist[y].abspath 642 | -- Join the path into their supplied rename, so that we have an absolute path 643 | local new_path = dirname_and_join(old_path, new_name) 644 | -- Use Go's os package for renaming the file/dir 645 | local golib_os = import("os") 646 | -- Actually rename the file 647 | local log_out = golib_os.Rename(old_path, new_path) 648 | -- Output the log, if any, of the rename 649 | if log_out ~= nil then 650 | micro.Log("Rename log: ", log_out) 651 | end 652 | 653 | -- Check if the rename worked 654 | if not path_exists(new_path) then 655 | micro.InfoBar():Error("Path doesn't exist after rename!") 656 | return 657 | end 658 | 659 | -- NOTE: doesn't alphabetically sort after refresh, but it probably should 660 | -- Replace the old path with the new path 661 | scanlist[y].abspath = new_path 662 | -- Refresh the tree with our new name 663 | refresh_and_select() 664 | end 665 | 666 | -- Prompts the user for the file/dir name, then creates the file/dir using Go's os package 667 | local function create_filedir(filedir_name, make_dir) 668 | if micro.CurPane() ~= tree_view then 669 | micro.InfoBar():Message("You can't create a file/dir if your cursor isn't in the tree!") 670 | return 671 | end 672 | 673 | -- Safety check they passed a name 674 | if filedir_name == nil then 675 | micro.InfoBar():Error('You need to input a name when using "touch" or "mkdir"!') 676 | return 677 | end 678 | 679 | -- The target they're trying to create on top of/in/at/whatever 680 | local y = get_safe_y() 681 | -- Holds the path passed to Go for the eventual new file/dir 682 | local filedir_path 683 | -- A true/false if scanlist is empty 684 | local scanlist_empty = scanlist_is_empty() 685 | 686 | -- Check there's actually anything in the list, and that they're not on the ".." 687 | if not scanlist_empty and y ~= 0 then 688 | -- If they're inserting on a folder, don't strip its path 689 | if scanlist[y].dirmsg ~= "" then 690 | -- Join our new file/dir onto the dir 691 | filedir_path = filepath.Join(scanlist[y].abspath, filedir_name) 692 | else 693 | -- The current index is a file, so strip its name and join ours onto it 694 | filedir_path = dirname_and_join(scanlist[y].abspath, filedir_name) 695 | end 696 | else 697 | -- if nothing in the list, or cursor is on top of "..", use the current dir 698 | filedir_path = filepath.Join(current_dir, filedir_name) 699 | end 700 | 701 | -- Check if the name is already taken by a file/dir 702 | if path_exists(filedir_path) then 703 | micro.InfoBar():Error("You can't create a file/dir with a pre-existing name") 704 | return 705 | end 706 | 707 | -- Use Go's os package for creating the files 708 | local golib_os = import("os") 709 | -- Create the dir or file 710 | if make_dir then 711 | -- Creates the dir 712 | golib_os.Mkdir(filedir_path, golib_os.ModePerm) 713 | micro.Log("Filemanager created directory: " .. filedir_path) 714 | else 715 | -- Creates the file 716 | golib_os.Create(filedir_path) 717 | micro.Log("Filemanager created file: " .. filedir_path) 718 | end 719 | 720 | -- If the file we tried to make doesn't exist, fail 721 | if not path_exists(filedir_path) then 722 | micro.InfoBar():Error("The file/dir creation failed") 723 | 724 | return 725 | end 726 | 727 | -- Creates a sort of default object, to be modified below 728 | -- If creating a dir, use a "+" 729 | local new_filedir = new_listobj(filedir_path, (make_dir and "+" or ""), 0, 0) 730 | 731 | -- Refresh with our new value(s) 732 | local last_y 733 | 734 | -- Only insert to scanlist if not created into a compressed dir, since it'd be hidden if it was 735 | -- Wrap the below checks so a y=0 doesn't break something 736 | if not scanlist_empty and y ~= 0 then 737 | -- +1 so it's highlighting the new file/dir 738 | last_y = tree_view.Cursor.Loc.Y + 1 739 | 740 | -- Only actually add the object to the list if it's not created on an uncompressed folder 741 | if scanlist[y].dirmsg == "+" then 742 | -- Exit early, since it was created into an uncompressed folder 743 | 744 | return 745 | elseif scanlist[y].dirmsg == "-" then 746 | -- Check if created on top of an uncompressed folder 747 | -- Change ownership to the folder it was created on top of.. 748 | -- otherwise, the ownership would be incorrect 749 | new_filedir.owner = y 750 | -- We insert under the folder, so increment the indent 751 | new_filedir.indent = scanlist[y].indent + 1 752 | else 753 | -- This triggers if the cursor is on top of a file... 754 | -- so we copy the properties of it 755 | new_filedir.owner = scanlist[y].owner 756 | new_filedir.indent = scanlist[y].indent 757 | end 758 | 759 | -- A temporary table for adding our new object, and manipulation 760 | local new_table = {} 761 | -- Insert the new file/dir, and update ownership of everything below it 762 | for i = 1, #scanlist do 763 | -- Don't use i as index, as it will be off by one on the next pass after below "i == y" 764 | new_table[#new_table + 1] = scanlist[i] 765 | if i == y then 766 | -- Insert our new file/dir (below the last item) 767 | new_table[#new_table + 1] = new_filedir 768 | -- Increase ownership of everything below it, since we're inserting 769 | -- Basically "moving down" everything below y, so ownership needs to increase on everything 770 | for inner_i = y + 1, #scanlist do 771 | -- When root not pushed by inserting, don't change its ownership 772 | -- This also has a dual-purpose to make it not effect root file/dirs 773 | -- since y is always >= 3 774 | if scanlist[inner_i].owner > y then 775 | -- Increase each indicies ownership by 1 since we're only inserting 1 file/dir 776 | scanlist[inner_i]:increase_owner(1) 777 | end 778 | end 779 | end 780 | end 781 | -- Update the scanlist with the new object & updated ownerships 782 | scanlist = new_table 783 | else 784 | -- The scanlist is empty (or cursor is on ".."), so we add on our new file/dir at the bottom 785 | scanlist[#scanlist + 1] = new_filedir 786 | -- Add current position so it takes into account where we are 787 | last_y = #scanlist + tree_view.Cursor.Loc.Y 788 | end 789 | 790 | refresh_view() 791 | select_line(last_y) 792 | end 793 | 794 | -- Triggered with "touch filename" 795 | function new_file(bp, args) 796 | 797 | -- Safety check they actually passed a name 798 | if #args < 1 then 799 | micro.InfoBar():Error('When using "touch" you need to input a file name') 800 | return 801 | end 802 | 803 | local file_name = args[1] 804 | 805 | -- False because not a dir 806 | create_filedir(file_name, false) 807 | end 808 | 809 | -- Triggered with "mkdir dirname" 810 | function new_dir(bp, args) 811 | 812 | -- Safety check they actually passed a name 813 | if #args < 1 then 814 | micro.InfoBar():Error('When using "mkdir" you need to input a dir name') 815 | return 816 | end 817 | 818 | local dir_name = args[1] 819 | 820 | -- True because dir 821 | create_filedir(dir_name, true) 822 | end 823 | 824 | -- open_tree setup's the view 825 | local function open_tree() 826 | -- Open a new Vsplit (on the very left) 827 | micro.CurPane():VSplitIndex(buffer.NewBuffer("", "filemanager"), false) 828 | -- Save the new view so we can access it later 829 | tree_view = micro.CurPane() 830 | 831 | -- Set the width of tree_view to 30% & lock it 832 | tree_view:ResizePane(30) 833 | -- Set the type to unsavable 834 | -- tree_view.Buf.Type = buffer.BTLog 835 | tree_view.Buf.Type.Scratch = true 836 | tree_view.Buf.Type.Readonly = true 837 | 838 | -- Set the various display settings, but only on our view (by using SetLocalOption instead of SetOption) 839 | -- NOTE: Micro requires the true/false to be a string 840 | -- Softwrap long strings (the file/dir paths) 841 | tree_view.Buf:SetOptionNative("softwrap", true) 842 | -- No line numbering 843 | tree_view.Buf:SetOptionNative("ruler", false) 844 | -- Is this needed with new non-savable settings from being "vtLog"? 845 | tree_view.Buf:SetOptionNative("autosave", false) 846 | -- Don't show the statusline to differentiate the view from normal views 847 | tree_view.Buf:SetOptionNative("statusformatr", "") 848 | tree_view.Buf:SetOptionNative("statusformatl", "filemanager") 849 | tree_view.Buf:SetOptionNative("scrollbar", false) 850 | 851 | -- Fill the scanlist, and then print its contents to tree_view 852 | update_current_dir(os.Getwd()) 853 | end 854 | 855 | -- close_tree will close the tree plugin view and release memory. 856 | local function close_tree() 857 | if tree_view ~= nil then 858 | tree_view:Quit() 859 | tree_view = nil 860 | clear_messenger() 861 | end 862 | end 863 | 864 | -- toggle_tree will toggle the tree view visible (create) and hide (delete). 865 | function toggle_tree() 866 | if tree_view == nil then 867 | open_tree() 868 | else 869 | close_tree() 870 | end 871 | end 872 | 873 | -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 874 | -- Functions exposed specifically for the user to bind 875 | -- Some are used in callbacks as well 876 | -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 877 | 878 | function uncompress_at_cursor() 879 | if micro.CurPane() == tree_view then 880 | uncompress_target(get_safe_y()) 881 | end 882 | end 883 | 884 | function compress_at_cursor() 885 | if micro.CurPane() == tree_view then 886 | -- False to not delete y 887 | compress_target(get_safe_y(), false) 888 | end 889 | end 890 | 891 | -- Goes up 1 visible directory (if any) 892 | -- Not local so it can be bound 893 | function goto_prev_dir() 894 | if micro.CurPane() ~= tree_view or scanlist_is_empty() then 895 | return 896 | end 897 | 898 | local cur_y = get_safe_y() 899 | -- If they try to run it on the ".." do nothing 900 | if cur_y ~= 0 then 901 | local move_count = 0 902 | for i = cur_y - 1, 1, -1 do 903 | move_count = move_count + 1 904 | -- If a dir, stop counting 905 | if scanlist[i].dirmsg ~= "" then 906 | -- Jump to its parent (the ownership) 907 | tree_view.Cursor:UpN(move_count) 908 | select_line() 909 | break 910 | end 911 | end 912 | end 913 | end 914 | 915 | -- Goes down 1 visible directory (if any) 916 | -- Not local so it can be bound 917 | function goto_next_dir() 918 | if micro.CurPane() ~= tree_view or scanlist_is_empty() then 919 | return 920 | end 921 | 922 | local cur_y = get_safe_y() 923 | local move_count = 0 924 | -- If they try to goto_next on "..", pretends the cursor is valid 925 | if cur_y == 0 then 926 | cur_y = 1 927 | move_count = 1 928 | end 929 | -- Only do anything if it's even possible for there to be another dir 930 | if cur_y < #scanlist then 931 | for i = cur_y + 1, #scanlist do 932 | move_count = move_count + 1 933 | -- If a dir, stop counting 934 | if scanlist[i].dirmsg ~= "" then 935 | -- Jump to its parent (the ownership) 936 | tree_view.Cursor:DownN(move_count) 937 | select_line() 938 | break 939 | end 940 | end 941 | end 942 | end 943 | 944 | -- Goes to the parent directory (if any) 945 | -- Not local so it can be keybound 946 | function goto_parent_dir() 947 | if micro.CurPane() ~= tree_view or scanlist_is_empty() then 948 | return 949 | end 950 | 951 | local cur_y = get_safe_y() 952 | -- Check if the cursor is even in a valid location for jumping to the owner 953 | if cur_y > 0 then 954 | -- Jump to its parent (the ownership) 955 | tree_view.Cursor:UpN(cur_y - scanlist[cur_y].owner) 956 | select_line() 957 | end 958 | end 959 | 960 | function try_open_at_cursor() 961 | if micro.CurPane() ~= tree_view or scanlist_is_empty() then 962 | return 963 | end 964 | 965 | try_open_at_y(tree_view.Cursor.Loc.Y) 966 | end 967 | 968 | -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 969 | -- Shorthand functions for actions to reduce repeat code 970 | -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 971 | 972 | -- Used to fail certain actions that we shouldn't allow on the tree_view 973 | local function false_if_tree(view) 974 | if view == tree_view then 975 | return false 976 | end 977 | end 978 | 979 | -- Select the line at the cursor 980 | local function selectline_if_tree(view) 981 | if view == tree_view then 982 | select_line() 983 | end 984 | end 985 | 986 | -- Move the cursor to the top, but don't allow the action 987 | local function aftermove_if_tree(view) 988 | if view == tree_view then 989 | if tree_view.Cursor.Loc.Y < 2 then 990 | -- If it went past the "..", move back onto it 991 | tree_view.Cursor:DownN(2 - tree_view.Cursor.Loc.Y) 992 | end 993 | select_line() 994 | end 995 | end 996 | 997 | local function clearselection_if_tree(view) 998 | if view == tree_view then 999 | -- Clear the selection when doing a find, so it doesn't copy the current line 1000 | tree_view.Cursor:ResetSelection() 1001 | end 1002 | end 1003 | 1004 | -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1005 | -- All the events for certain Micro keys go below here 1006 | -- Other than things we flat-out fail 1007 | -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1008 | 1009 | -- Close current 1010 | function preQuit(view) 1011 | if view == tree_view then 1012 | -- A fake quit function 1013 | close_tree() 1014 | -- Don't actually "quit", otherwise it closes everything without saving for some reason 1015 | return false 1016 | end 1017 | end 1018 | 1019 | -- Close all 1020 | function preQuitAll(view) 1021 | close_tree() 1022 | end 1023 | 1024 | -- FIXME: Workaround for the weird 2-index movement on cursordown 1025 | function preCursorDown(view) 1026 | if view == tree_view then 1027 | tree_view.Cursor:Down() 1028 | select_line() 1029 | -- Don't actually go down, as it moves 2 indicies for some reason 1030 | return false 1031 | end 1032 | end 1033 | 1034 | -- Up 1035 | function onCursorUp(view) 1036 | selectline_if_tree(view) 1037 | end 1038 | 1039 | -- Alt-Shift-{ 1040 | -- Go to target's parent directory (if exists) 1041 | function preParagraphPrevious(view) 1042 | if view == tree_view then 1043 | goto_prev_dir() 1044 | -- Don't actually do the action 1045 | return false 1046 | end 1047 | end 1048 | 1049 | -- Alt-Shift-} 1050 | -- Go to next dir (if exists) 1051 | function preParagraphNext(view) 1052 | if view == tree_view then 1053 | goto_next_dir() 1054 | -- Don't actually do the action 1055 | return false 1056 | end 1057 | end 1058 | 1059 | -- PageUp 1060 | function onCursorPageUp(view) 1061 | aftermove_if_tree(view) 1062 | end 1063 | 1064 | -- Ctrl-Up 1065 | function onCursorStart(view) 1066 | aftermove_if_tree(view) 1067 | end 1068 | 1069 | -- PageDown 1070 | function onCursorPageDown(view) 1071 | selectline_if_tree(view) 1072 | end 1073 | 1074 | -- Ctrl-Down 1075 | function onCursorEnd(view) 1076 | selectline_if_tree(view) 1077 | end 1078 | 1079 | function onNextSplit(view) 1080 | selectline_if_tree(view) 1081 | end 1082 | 1083 | function onPreviousSplit(view) 1084 | selectline_if_tree(view) 1085 | end 1086 | 1087 | -- On click, open at the click's y 1088 | function preMousePress(view, event) 1089 | if view == tree_view then 1090 | local x, y = event:Position() 1091 | -- Fixes the y because softwrap messes with it 1092 | local new_x, new_y = tree_view:GetMouseClickLocation(x, y) 1093 | -- Try to open whatever is at the click's y index 1094 | -- Will go into/back dirs based on what's clicked, nothing gets expanded 1095 | try_open_at_y(new_y) 1096 | -- Don't actually allow the mousepress to trigger, so we avoid highlighting stuff 1097 | return false 1098 | end 1099 | end 1100 | 1101 | -- Up 1102 | function preCursorUp(view) 1103 | if view == tree_view then 1104 | -- Disallow selecting past the ".." in the tree 1105 | if tree_view.Cursor.Loc.Y == 2 then 1106 | return false 1107 | end 1108 | end 1109 | end 1110 | 1111 | -- Left 1112 | function preCursorLeft(view) 1113 | if view == tree_view then 1114 | -- +1 because of Go's zero-based index 1115 | -- False to not delete y 1116 | compress_target(get_safe_y(), false) 1117 | -- Don't actually move the cursor, as it messes with selection 1118 | return false 1119 | end 1120 | end 1121 | 1122 | -- Right 1123 | function preCursorRight(view) 1124 | if view == tree_view then 1125 | -- +1 because of Go's zero-based index 1126 | uncompress_target(get_safe_y()) 1127 | -- Don't actually move the cursor, as it messes with selection 1128 | return false 1129 | end 1130 | end 1131 | 1132 | -- Workaround for tab getting inserted into opened files 1133 | -- Ref https://github.com/zyedidia/micro/issues/992 1134 | local tab_pressed = false 1135 | 1136 | -- Tab 1137 | function preIndentSelection(view) 1138 | if view == tree_view then 1139 | tab_pressed = true 1140 | -- Open the file 1141 | -- Using tab instead of enter, since enter won't work with Readonly 1142 | try_open_at_y(tree_view.Cursor.Loc.Y) 1143 | -- Don't actually insert a tab 1144 | return false 1145 | end 1146 | end 1147 | 1148 | -- Workaround for tab getting inserted into opened files 1149 | -- Ref https://github.com/zyedidia/micro/issues/992 1150 | function preInsertTab(view) 1151 | if tab_pressed then 1152 | tab_pressed = false 1153 | return false 1154 | end 1155 | end 1156 | function preInsertNewline(view) 1157 | if view == tree_view then 1158 | return false 1159 | end 1160 | return true 1161 | end 1162 | -- CtrlL 1163 | function onJumpLine(view) 1164 | -- Highlight the line after jumping to it 1165 | -- Also moves you to index 3 (2 in zero-base) if you went to the first 2 lines 1166 | aftermove_if_tree(view) 1167 | end 1168 | 1169 | -- ShiftUp 1170 | function preSelectUp(view) 1171 | if view == tree_view then 1172 | -- Go to the file/dir's parent dir (if any) 1173 | goto_parent_dir() 1174 | -- Don't actually selectup 1175 | return false 1176 | end 1177 | end 1178 | 1179 | -- CtrlF 1180 | function preFind(view) 1181 | -- Since something is always selected, clear before a find 1182 | -- Prevents copying the selection into the find input 1183 | clearselection_if_tree(view) 1184 | end 1185 | 1186 | -- FIXME: doesn't work for whatever reason 1187 | function onFind(view) 1188 | -- Select the whole line after a find, instead of just the input txt 1189 | selectline_if_tree(view) 1190 | end 1191 | 1192 | -- CtrlN after CtrlF 1193 | function onFindNext(view) 1194 | selectline_if_tree(view) 1195 | end 1196 | 1197 | -- CtrlP after CtrlF 1198 | function onFindPrevious(view) 1199 | selectline_if_tree(view) 1200 | end 1201 | 1202 | -- NOTE: This is a workaround for "cd" not having its own callback 1203 | local precmd_dir 1204 | 1205 | function preCommandMode(view) 1206 | precmd_dir = os.Getwd() 1207 | end 1208 | 1209 | -- Update the current dir when using "cd" 1210 | function onCommandMode(view) 1211 | local new_dir = os.Getwd() 1212 | -- Only do anything if the tree is open, and they didn't cd to nothing 1213 | if tree_view ~= nil and new_dir ~= precmd_dir and new_dir ~= current_dir then 1214 | update_current_dir(new_dir) 1215 | end 1216 | end 1217 | 1218 | ------------------------------------------------------------------ 1219 | -- Fail a bunch of useless actions 1220 | -- Some of these need to be removed (read-only makes some useless) 1221 | ------------------------------------------------------------------ 1222 | 1223 | function preStartOfLine(view) 1224 | return false_if_tree(view) 1225 | end 1226 | 1227 | function preStartOfText(view) 1228 | return false_if_tree(view) 1229 | end 1230 | 1231 | function preEndOfLine(view) 1232 | return false_if_tree(view) 1233 | end 1234 | 1235 | function preMoveLinesDown(view) 1236 | return false_if_tree(view) 1237 | end 1238 | 1239 | function preMoveLinesUp(view) 1240 | return false_if_tree(view) 1241 | end 1242 | 1243 | function preWordRight(view) 1244 | return false_if_tree(view) 1245 | end 1246 | 1247 | function preWordLeft(view) 1248 | return false_if_tree(view) 1249 | end 1250 | 1251 | function preSelectDown(view) 1252 | return false_if_tree(view) 1253 | end 1254 | 1255 | function preSelectLeft(view) 1256 | return false_if_tree(view) 1257 | end 1258 | 1259 | function preSelectRight(view) 1260 | return false_if_tree(view) 1261 | end 1262 | 1263 | function preSelectWordRight(view) 1264 | return false_if_tree(view) 1265 | end 1266 | 1267 | function preSelectWordLeft(view) 1268 | return false_if_tree(view) 1269 | end 1270 | 1271 | function preSelectToStartOfLine(view) 1272 | return false_if_tree(view) 1273 | end 1274 | 1275 | function preSelectToStartOfText(view) 1276 | return false_if_tree(view) 1277 | end 1278 | 1279 | function preSelectToEndOfLine(view) 1280 | return false_if_tree(view) 1281 | end 1282 | 1283 | function preSelectToStart(view) 1284 | return false_if_tree(view) 1285 | end 1286 | 1287 | function preSelectToEnd(view) 1288 | return false_if_tree(view) 1289 | end 1290 | 1291 | function preDeleteWordLeft(view) 1292 | return false_if_tree(view) 1293 | end 1294 | 1295 | function preDeleteWordRight(view) 1296 | return false_if_tree(view) 1297 | end 1298 | 1299 | function preOutdentSelection(view) 1300 | return false_if_tree(view) 1301 | end 1302 | 1303 | function preOutdentLine(view) 1304 | return false_if_tree(view) 1305 | end 1306 | 1307 | function preSave(view) 1308 | return false_if_tree(view) 1309 | end 1310 | 1311 | function preCut(view) 1312 | return false_if_tree(view) 1313 | end 1314 | 1315 | function preCutLine(view) 1316 | return false_if_tree(view) 1317 | end 1318 | 1319 | function preDuplicateLine(view) 1320 | return false_if_tree(view) 1321 | end 1322 | 1323 | function prePaste(view) 1324 | return false_if_tree(view) 1325 | end 1326 | 1327 | function prePastePrimary(view) 1328 | return false_if_tree(view) 1329 | end 1330 | 1331 | function preMouseMultiCursor(view) 1332 | return false_if_tree(view) 1333 | end 1334 | 1335 | function preSpawnMultiCursor(view) 1336 | return false_if_tree(view) 1337 | end 1338 | 1339 | function preSelectAll(view) 1340 | return false_if_tree(view) 1341 | end 1342 | 1343 | function init() 1344 | -- Let the user disable showing of dotfiles like ".editorconfig" or ".DS_STORE" 1345 | config.RegisterCommonOption("filemanager", "showdotfiles", true) 1346 | -- Let the user disable showing files ignored by the VCS (i.e. gitignored) 1347 | config.RegisterCommonOption("filemanager", "showignored", true) 1348 | -- Let the user disable going to parent directory via left arrow key when file selected (not directory) 1349 | config.RegisterCommonOption("filemanager", "compressparent", true) 1350 | -- Let the user choose to list sub-folders first when listing the contents of a folder 1351 | config.RegisterCommonOption("filemanager", "foldersfirst", true) 1352 | -- Lets the user have the filetree auto-open any time Micro is opened 1353 | -- false by default, as it's a rather noticable user-facing change 1354 | config.RegisterCommonOption("filemanager", "openonstart", false) 1355 | 1356 | -- Open/close the tree view 1357 | config.MakeCommand("tree", toggle_tree, config.NoComplete) 1358 | -- Rename the file/dir under the cursor 1359 | config.MakeCommand("rename", rename_at_cursor, config.NoComplete) 1360 | -- Create a new file 1361 | config.MakeCommand("touch", new_file, config.NoComplete) 1362 | -- Create a new dir 1363 | config.MakeCommand("mkdir", new_dir, config.NoComplete) 1364 | -- Delete a file/dir, and anything contained in it if it's a dir 1365 | config.MakeCommand("rm", prompt_delete_at_cursor, config.NoComplete) 1366 | -- Adds colors to the ".." and any dir's in the tree view via syntax highlighting 1367 | -- TODO: Change it to work with git, based on untracked/changed/added/whatever 1368 | config.AddRuntimeFile("filemanager", config.RTSyntax, "syntax.yaml") 1369 | 1370 | -- NOTE: This must be below the syntax load command or coloring won't work 1371 | -- Just auto-open if the option is enabled 1372 | -- This will run when the plugin first loads 1373 | if config.GetGlobalOption("filemanager.openonstart") then 1374 | -- Check for safety on the off-chance someone's init.lua breaks this 1375 | if tree_view == nil then 1376 | open_tree() 1377 | -- Puts the cursor back in the empty view that initially spawns 1378 | -- This is so the cursor isn't sitting in the tree view at startup 1379 | micro.CurPane():NextSplit() 1380 | else 1381 | -- Log error so they can fix it 1382 | micro.Log( 1383 | "Warning: filemanager.openonstart was enabled, but somehow the tree was already open so the option was ignored." 1384 | ) 1385 | end 1386 | end 1387 | end 1388 | -------------------------------------------------------------------------------- /.config/micro/plug/filemanager/repo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "filemanager", 4 | "Description": "File manager for Micro", 5 | "Tags": ["filetree", "filemanager", "file", "manager"], 6 | "Website": "", 7 | "Versions": [ 8 | { 9 | "Version": "2.1.1", 10 | "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v2.1.1.zip", 11 | "Require": { 12 | "micro": ">=1.3.2" 13 | } 14 | }, 15 | { 16 | "Version": "3.1.0", 17 | "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.1.0.zip", 18 | "Require": { 19 | "micro": ">=1.3.5" 20 | } 21 | }, 22 | { 23 | "Version": "3.4.0", 24 | "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.4.0.zip", 25 | "Require": { 26 | "micro": ">=1.4.1" 27 | } 28 | }, 29 | { 30 | "Version": "3.5.0", 31 | "Url": "https://github.com/micro-editor/updated-plugins/releases/download/v1.0.0/filemanager-3.5.0.zip", 32 | "Require": { 33 | "micro": ">=2.0.0-1" 34 | } 35 | }, 36 | { 37 | "Version": "3.5.1", 38 | "Url": "https://github.com/micro-editor/updated-plugins/releases/download/v1.0.0/filemanager-3.5.1.zip", 39 | "Require": { 40 | "micro": ">=2.0.0-1" 41 | } 42 | } 43 | ] 44 | } 45 | ] 46 | -------------------------------------------------------------------------------- /.config/micro/plug/filemanager/syntax.yaml: -------------------------------------------------------------------------------- 1 | filetype: filemanager 2 | 3 | detect: 4 | filename: "^filemanager$" 5 | 6 | rules: 7 | # The "..", the separator line thing, and directories 8 | # Optionally, add this below to highlight the ascii line: "^─*$" 9 | - special: "^\\.\\.$|\\-\\s.*|\\+\\s.*" 10 | -------------------------------------------------------------------------------- /.config/micro/plug/manipulator/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | -------------------------------------------------------------------------------- /.config/micro/plug/manipulator/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nicolai Søborg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.config/micro/plug/manipulator/README.md: -------------------------------------------------------------------------------- 1 | # Manipulator Plugin 2 | 3 | This is an simple plugin to extend text manipulation in Micro. 4 | 5 | ## Keybindings 6 | By default no keybindings exist, but you can easily modify that 7 | in your `bindings.json` file: 8 | 9 | ```json 10 | { 11 | "Ctrl-L": "manipulator.lower" 12 | } 13 | ``` 14 | 15 | You can also execute a command which will do the same thing as 16 | the binding: 17 | 18 | ``` 19 | > lower 20 | ``` 21 | 22 | If you have a selection, the plugin will change all of the 23 | selection. 24 | 25 | The following commands currently exists: 26 | * `upper`: UPPERCASE 27 | * `lower`: lowercase 28 | * `reverse`: Reverses 29 | * `base64enc`: Base64 encodes 30 | * `base64dec`: Base64 decodes 31 | * `rot13`: ROT-13 32 | * `incNum`: Increase number by one 33 | * `decNum`: Decrease number by one 34 | * `capital`: Capitalize First Letter 35 | * `brace`: Adds brackets around selection 36 | * `curly`: Curly brackets (`{ }`) 37 | * `square`: Square brackets (`[ ]`) 38 | * `angle`: Angle brackets (`< >`) 39 | * `dquote`: Double quotes (`" "`) 40 | * `squote`: Single quotes (`' '`) 41 | 42 | ## Issues 43 | 44 | Please use the issue tracker if you have any issues or 45 | feature requests! 46 | 47 | 48 | ## Demo 49 | 50 | ![Demo](demo.gif "Demo: Using a few of the commands") 51 | -------------------------------------------------------------------------------- /.config/micro/plug/manipulator/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aureliojargas/dotfiles/f35732301f6592415f45038c00207c7e463b685e/.config/micro/plug/manipulator/demo.gif -------------------------------------------------------------------------------- /.config/micro/plug/manipulator/demo.txt: -------------------------------------------------------------------------------- 1 | THIS TEXT SHOULDN'T BE ALL UPPERCASE 2 | Write const all uppercase, e.g. max_connections 3 | This should be two: 1 <== you can also decrease! 4 | 5 | base64(rot13(txt)): 6 | ZmhjcmUgZnJwZXJn 7 | -------------------------------------------------------------------------------- /.config/micro/plug/manipulator/help/manipulator.md: -------------------------------------------------------------------------------- 1 | # Manipulator Plugin 2 | 3 | This is an simple plugin to extend text manipulation in Micro. 4 | 5 | ## Keybindings 6 | By default no keybindings exist, but you can easily modify that 7 | in your `bindings.json` file: 8 | 9 | ```json 10 | { 11 | "Ctrl-L": "manipulator.lower" 12 | } 13 | ``` 14 | 15 | You can also execute a command which will do the same thing as 16 | the binding: 17 | 18 | ``` 19 | > lower 20 | ``` 21 | 22 | If you have a selection, the plugin will change all of the 23 | selection. 24 | 25 | The following commands currently exists: 26 | * `upper`: UPPERCASE 27 | * `lower`: lowercase 28 | * `reverse`: Reverses 29 | * `base64enc`: Base64 encodes 30 | * `base64dec`: Base64 decodes 31 | * `rot13`: ROT-13 32 | * `incNum`: Increase number by one 33 | * `decNum`: Decrease number by one 34 | * `capital`: Capitalize First Letter 35 | * `brace`: Adds brackets around selection 36 | * `curly`: Curly brackets (`{ }`) 37 | * `square`: Square brackets (`[ ]`) 38 | * `angle`: Angle brackets (`< >`) 39 | * `dquote`: Double quotes (`" "`) 40 | * `squote`: Single quotes (`' '`) 41 | 42 | ## Issues 43 | 44 | Please use the issue tracker if you have any issues or 45 | feature requests! 46 | 47 | 48 | ## Demo 49 | 50 | ![Demo](demo.gif "Demo: Using a few of the commands") 51 | -------------------------------------------------------------------------------- /.config/micro/plug/manipulator/manipulator.lua: -------------------------------------------------------------------------------- 1 | VERSION = "1.4.0" 2 | 3 | local micro = import("micro") 4 | local config = import("micro/config") 5 | local buffer = import("micro/buffer") 6 | 7 | -- Returns Loc-tuple w/ current marked text or whole line (begin, end) 8 | function getTextLoc() 9 | local v = micro.CurPane() 10 | local a, b, c = nil, nil, v.Cursor 11 | if c:HasSelection() then 12 | if c.CurSelection[1]:GreaterThan(-c.CurSelection[2]) then 13 | a, b = c.CurSelection[2], c.CurSelection[1] 14 | else 15 | a, b = c.CurSelection[1], c.CurSelection[2] 16 | end 17 | else 18 | local eol = string.len(v.Buf:Line(c.Loc.Y)) 19 | a, b = c.Loc, buffer.Loc(eol, c.Y) 20 | end 21 | return buffer.Loc(a.X, a.Y), buffer.Loc(b.X, b.Y) 22 | end 23 | 24 | -- Returns the current marked text or whole line 25 | function getText(a, b) 26 | local txt, buf = {}, micro.CurPane().Buf 27 | 28 | -- Editing a single line? 29 | if a.Y == b.Y then 30 | return buf:Line(a.Y):sub(a.X+1, b.X) 31 | end 32 | 33 | -- Add first part of text selection (a.X+1 as Lua is 1-indexed) 34 | table.insert(txt, buf:Line(a.Y):sub(a.X+1)) 35 | 36 | -- Stuff in the middle 37 | for lineNo = a.Y+1, b.Y-1 do 38 | table.insert(txt, buf:Line(lineNo)) 39 | end 40 | 41 | -- Insert last part of selection 42 | table.insert(txt, buf:Line(b.Y):sub(1, b.X)) 43 | 44 | return table.concat(txt, "\n") 45 | end 46 | 47 | -- Calls 'manipulator'-function on text matching 'regex' 48 | function manipulate(regex, manipulator, num) 49 | local num = math.inf or num 50 | local v = micro.CurPane() 51 | local a, b = getTextLoc() 52 | 53 | local oldTxt = getText(a,b) 54 | 55 | local newTxt = string.gsub(oldTxt, regex, manipulator, num) 56 | v.Buf:Replace(a, b, newTxt) 57 | 58 | -- Fix selection, if transformation changes text length (e.g. base64) 59 | local d = string.len(newTxt) - string.len(oldTxt) 60 | if d ~= 0 then 61 | local c = v.Cursor 62 | if c:HasSelection() then 63 | if c.CurSelection[1]:GreaterThan(-c.CurSelection[2]) then 64 | c.CurSelection[1].X = c.CurSelection[1].X - d 65 | else 66 | c.CurSelection[2].X = c.CurSelection[2].X + d 67 | end 68 | end 69 | end 70 | 71 | --v.Cursor:Relocate() 72 | --v.Cursor.LastVisualX = v.Cursor:GetVisualX() 73 | end 74 | 75 | 76 | --[[ DEFINE FUNCTIONS ]]-- 77 | 78 | function rot13() manipulate("[%a]", 79 | function (txt) 80 | local result, lower, upper = {}, string.byte('a'), string.byte('A') 81 | for c in txt:gmatch(".") do 82 | local offset = string.lower(c) == c and lower or upper 83 | local p = ((c:byte() - offset + 13) % 26) + offset 84 | table.insert(result, string.char(p)) 85 | end 86 | return table.concat(result, "") 87 | end 88 | ) end 89 | 90 | function incNum() manipulate("[%d]", 91 | function (txt) return tonumber(txt)+1 end 92 | ) end 93 | 94 | function decNum() manipulate("[%d]", 95 | function (txt) return tonumber(txt)-1 end 96 | ) end 97 | 98 | -- Credit: http://lua-users.org/wiki/BaseSixtyFour 99 | function base64enc() manipulate(".*", 100 | function (data) 101 | local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' 102 | return ((data:gsub('.', function(x) 103 | local r,b='',x:byte() 104 | for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end 105 | return r; 106 | end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) 107 | if (#x < 6) then return '' end 108 | local c=0 109 | for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end 110 | return b:sub(c+1,c+1) 111 | end)..({ '', '==', '=' })[#data%3+1]) 112 | end 113 | ) end 114 | 115 | function base64dec() manipulate(".*", 116 | function (data) 117 | local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' 118 | data = string.gsub(data, '[^'..b..'=]', '') 119 | return (data:gsub('.', function(x) 120 | if (x == '=') then return '' end 121 | local r,f='',(b:find(x)-1) 122 | for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end 123 | return r; 124 | end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) 125 | if (#x ~= 8) then return '' end 126 | local c=0 127 | for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end 128 | return string.char(c) 129 | end)) 130 | end 131 | ) end 132 | 133 | -- Credit: http://lua-users.org/wiki/StringRecipes 134 | function capital() manipulate("(%a)([%w_']*)", 135 | function (first, rest) 136 | return first:upper() .. rest:lower() 137 | end 138 | ) end 139 | 140 | function init() 141 | config.MakeCommand("capital", capital, config.NoComplete) 142 | -- Thanks marinopposite 143 | config.MakeCommand("brace", function() manipulate(".*", "(%1)", 1) end, config.NoComplete) 144 | config.MakeCommand("curly", function() manipulate(".*", "{%1}", 1) end, config.NoComplete) 145 | config.MakeCommand("square", function() manipulate(".*", "[%1]", 1) end, config.NoComplete) 146 | config.MakeCommand("dquote", function() manipulate(".*", '"%1"', 1) end, config.NoComplete) 147 | config.MakeCommand("squote", function() manipulate(".*", "'%1'", 1) end, config.NoComplete) 148 | config.MakeCommand("angle", function() manipulate(".*", "<%1>", 1) end, config.NoComplete) 149 | config.MakeCommand("base64dec", base64dec, config.NoComplete) 150 | config.MakeCommand("base64enc", base64enc, config.NoComplete) 151 | config.MakeCommand("decNum", decNum, config.NoComplete) 152 | config.MakeCommand("incNum", incNum, config.NoComplete) 153 | config.MakeCommand("rot13", rot13, config.NoComplete) 154 | config.MakeCommand("upper", function() manipulate("[%a]", string.upper) end, config.NoComplete) 155 | config.MakeCommand("lower", function() manipulate("[%a]", string.lower) end, config.NoComplete) 156 | config.MakeCommand("reverse", function() manipulate(".*", string.reverse) end, config.NoComplete) 157 | 158 | config.AddRuntimeFile("manipulator", config.RTHelp, "help/manipulator.md") 159 | end 160 | -------------------------------------------------------------------------------- /.config/micro/plug/manipulator/repo.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "Name": "manipulator", 3 | "Description": "Plugin to do various kind of modifications to text in Micro", 4 | "Website": "https://github.com/NicolaiSoeborg/manipulator-plugin", 5 | "Tags": ["text", "manipulation", "base64", "rot13", "bracket", "Capital", "uppercase", "lowercase"], 6 | "Versions": [ 7 | { 8 | "Version": "1.4.0", 9 | "Url": "https://github.com/NicolaiSoeborg/manipulator-plugin/archive/v1.4.0.zip", 10 | "Require": { 11 | "micro": ">=2.0.0-1" 12 | } 13 | }, 14 | { 15 | "Version": "1.3.2", 16 | "Url": "https://github.com/NicolaiSoeborg/manipulator-plugin/archive/v1.3.2.zip", 17 | "Require": { 18 | "micro": ">=1.0.0" 19 | } 20 | } 21 | ] 22 | }] 23 | -------------------------------------------------------------------------------- /.config/micro/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "autosave": 5, 3 | "backup": false, 4 | "colorcolumn": 80, 5 | "colorscheme": "bubblegum", 6 | "diffgutter": true, 7 | "fastdirty": true, 8 | "ft:git-commit": { 9 | "colorcolumn": 72, 10 | "filetype": "markdown" 11 | }, 12 | "hlsearch": true, 13 | "parsecursor": true, 14 | "rmtrailingws": true, 15 | "savecursor": true, 16 | "softwrap": true, 17 | "tabsize": 8 18 | } 19 | -------------------------------------------------------------------------------- /.gemrc: -------------------------------------------------------------------------------- 1 | gem: --no-document --user-install 2 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | # https://git-scm.com/docs/git-config#_configuration_file 2 | 3 | [alias] # https://git.wiki.kernel.org/index.php/Aliases 4 | 5 | # Using $GIT_PREFIX to workaround aliases running from the repository root 6 | # https://til.codeinthehole.com/posts/that-git-aliases-that-run-an-external-command-are-run-from-the-repository-root/ 7 | 8 | co = checkout 9 | 10 | commit-message = !sh -c 'git-pre-commit && git commit -m \"$*\"' - 11 | 12 | cp = cherry-pick 13 | 14 | diffw = diff -w --ignore-blank-lines 15 | 16 | g = grep --extended-regexp --break --heading --line-number --ignore-case --show-function 17 | 18 | grep-filename = !sh -c 'git ls-files | grep --color=always $@' - 19 | 20 | # hash, date, email, summary 21 | log4 = log -n 7 --oneline --reverse --date=short \ 22 | --pretty=format:'%Cblue%h%Creset %C(yellow)%ad%Creset %Cred%<(22,trunc)%ae%Creset %s' 23 | 24 | # hash, date, summary 25 | log3 = !sh -c 'git log4 --color=always $@ | tr -s \" \" | cut -d \" \" -f 1,2,4-' - 26 | 27 | # hash, summary 28 | log2 = !sh -c 'git log3 $@ | cut -d \" \" -f 1,4-' - 29 | 30 | log-graph = log --oneline --decorate --all --graph 31 | 32 | new = !sh -c 'git log4 -n 999 @{1}..@{0}' # What's new since the last git pull? 33 | 34 | # git sed 's/foo/bar/g' 35 | sed = "!f() { cd ${GIT_PREFIX:-./}; git grep -z -l . | xargs -0 gsed -E -i \"$1\"; }; f" 36 | 37 | # git sed2 's/foo/bar/g' '*.py' 38 | sed2 = "!f() { cd ${GIT_PREFIX:-./}; git ls-files -z -- \"${2:-*}\" | xargs -0 -n 1 gsed -E -i \"$1\"; }; f" 39 | 40 | uncommit = reset --soft HEAD^ 41 | 42 | unstage = reset HEAD 43 | 44 | worddiff = diff --color-words 45 | 46 | [commit] 47 | status = true # show `git status` output in commit template 48 | 49 | [core] 50 | # editor = ed --loose-exit-status # ed challenge :) 51 | editor = micro 52 | # pager = diff-so-fancy | less --tabs=4 -RFX # word-highlight diffs 53 | pager = delta 54 | excludesfile = ~/.gitignore_global 55 | 56 | [blame] 57 | coloring = highlightRecent # --color-by-age 58 | 59 | [branch] 60 | sort = committerdate # show recently changed branches at bottom 61 | 62 | [color] 63 | ui = auto 64 | 65 | [diff] 66 | # Color moved lines differently 67 | # https://dev.to/cloudx/how-to-color-the-moved-code-in-git-10ei 68 | colormoved = default 69 | colormovedws = allow-indentation-change 70 | 71 | [delta] # https://github.com/dandavison/delta 72 | light = false 73 | syntax-theme = GitHub # GitHub, Monokai Extended, ansi, zenburn 74 | line-numbers = false 75 | side-by-side = false 76 | whitespace-error-style = red reverse # trailing spaces are red 77 | navigate = true # n and N move between diff sections 78 | 79 | [interactive] 80 | diffFilter = delta --color-only 81 | 82 | [help] 83 | autocorrect = 1 # fix typos 84 | 85 | # Customize the git info in the shell prompt (bash, fish) 86 | # https://fishshell.com/docs/current/cmds/fish_git_prompt.html 87 | [bash] 88 | showUntrackedFiles = false # …↑1 89 | showDirtyState = false # ●1✚1 90 | 91 | [pull] 92 | ff = only 93 | 94 | [push] 95 | # https://git-scm.com/docs/git-config.html#git-config-pushdefault 96 | default = current # default in Git 1.x 97 | 98 | [remote "origin"] 99 | prune = true # Autoremove already deleted remote branches 100 | 101 | [remote "upstream"] 102 | prune = true # Autoremove already deleted remote branches 103 | 104 | # Use separate files for username, credentials, etc 105 | # https://git-scm.com/docs/git-config#_includes 106 | [includeIf "gitdir:~/k/a/"] 107 | path = ~/.gitconfig.personal 108 | [includeIf "gitdir:~/k/w/"] 109 | path = ~/.gitconfig.work 110 | 111 | # Some useful Git commands that I always have to search the web 112 | # 113 | # All commits whose tree has this pattern 114 | # git rev-list --all | xargs git grep PATTERN 115 | # 116 | # All commits that added/removed/edited lines with this pattern 117 | # git log --pickaxe-regex -S PATTERN # added/removed 118 | # git log -G PATTERN # added/removed/edited 119 | # 120 | # Log for a removed file 121 | # git log --full-history -- REMOVED-FILE-PATH 122 | # 123 | # Change commit date (both) 124 | # git rebase -i, edit the desired commit 125 | # d="2017-10-08T09:51:07" # gl --date=iso-strict to get it from a commit 126 | # GIT_COMMITTER_DATE="$d" git commit --amend --no-edit --date="$d" 127 | # git rebase --continue 128 | # Note that any `git rebase` on those commits will have CommitterDate set 129 | # to the current date. To avoid that, use --committer-date-is-author-date 130 | # 131 | # Rename a file in history 132 | # git rebase -i, edit the commit that added the file, rename it, amend. 133 | -------------------------------------------------------------------------------- /.gitconfig.local.template: -------------------------------------------------------------------------------- 1 | [user] 2 | name = John Doe 3 | email = john@example.com 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # auto generated files inside symlinked folders 2 | /.termux/shell 3 | /.vim/.netrwhist 4 | /.vim/pack/ 5 | 6 | # other local-only files 7 | /.vscode 8 | /.a.md 9 | -------------------------------------------------------------------------------- /.inputrc: -------------------------------------------------------------------------------- 1 | # TAB completion 2 | set completion-ignore-case on 3 | 4 | # No beeps 5 | set bell-style visible 6 | -------------------------------------------------------------------------------- /.screenrc: -------------------------------------------------------------------------------- 1 | # Use fish as the default shell 2 | shell /usr/bin/fish 3 | 4 | # Autodetach session on hangup instead of terminating screen completely 5 | autodetach on 6 | 7 | # Define a bigger scrollback, default is 100 lines 8 | defscrollback 9999 9 | 10 | # Enable mouse scrolling and scroll bar history scrolling 11 | # https://unix.stackexchange.com/a/40246/288927 12 | termcapinfo xterm* ti@:te@ 13 | 14 | # Turn off annoyances 15 | startup_message off 16 | vbell off 17 | bell_msg "" 18 | 19 | # Split related 20 | caption splitonly "%2n %t" 21 | bind 'R' wrap 22 | bind 'r' resize 23 | bind '+' resize +5 24 | bind '-' resize -5 25 | 26 | # Blue status bar, white text 27 | hardstatus alwayslastline "%w" 28 | sorendition 00 47 29 | # 30 | # sorendition 31 | # attributes: 32 | # 0 normal, 1 invert, 2 bright bg, 3 invert+bright bg (1+2), 5 bright fg 33 | # colors: 34 | # 0 black, 1 red, 2 green, 3 brown, 4 blue, 5 magenta, 6 cyan, 7 silver 35 | # examples: 36 | # 00 02 - black bg (0), green fg (2), no attributes (00) 37 | # 20 30 - brown bg (3), black fg (0), bright bg (2) -> turns yellow 38 | # 35 74 - silver bg (7), blue fg (4), invert+bright bg (3), bright fg (5) 39 | -------------------------------------------------------------------------------- /.termux/termux.properties: -------------------------------------------------------------------------------- 1 | # https://wiki.termux.com/wiki/Touch_Keyboard 2 | # https://wiki.termux.com/wiki/Terminal_Settings 3 | # https://github.com/termux/termux-tools/blob/master/termux.properties 4 | # After editing run: termux-reload-settings 5 | 6 | # Two extra top rows for the on-screen keyboard 7 | # Also available: PGUP PGDN 8 | extra-keys = [['ESC','/','~','HOME','UP','END'],['TAB','CTRL','ALT','LEFT','DOWN','RIGHT']] 9 | 10 | # When using an external keyboard, the top rows are not necessary 11 | #extra-keys = [] 12 | -------------------------------------------------------------------------------- /.tilix.dconf: -------------------------------------------------------------------------------- 1 | [/] 2 | accelerators-enabled=true 3 | copy-on-select=true 4 | enable-wide-handle=false 5 | paste-advanced-default=false 6 | sidebar-on-right=false 7 | terminal-title-show-when-single=false 8 | terminal-title-style='small' 9 | theme-variant='dark' 10 | use-tabs=false 11 | window-save-state=true 12 | window-state=132 13 | window-style='disable-csd' 14 | 15 | [keybindings] 16 | app-new-session='disabled' 17 | app-new-window='disabled' 18 | nautilus-open='disabled' 19 | session-add-auto='disabled' 20 | session-add-down='disabled' 21 | session-add-right='disabled' 22 | session-close='disabled' 23 | session-open='disabled' 24 | session-resize-terminal-down='disabled' 25 | session-resize-terminal-left='disabled' 26 | session-resize-terminal-right='disabled' 27 | session-resize-terminal-up='disabled' 28 | session-save='disabled' 29 | session-switch-to-terminal-0='disabled' 30 | session-switch-to-terminal-1='disabled' 31 | session-switch-to-terminal-2='disabled' 32 | session-switch-to-terminal-3='disabled' 33 | session-switch-to-terminal-4='disabled' 34 | session-switch-to-terminal-5='disabled' 35 | session-switch-to-terminal-6='disabled' 36 | session-switch-to-terminal-7='disabled' 37 | session-switch-to-terminal-8='disabled' 38 | session-switch-to-terminal-9='disabled' 39 | session-switch-to-terminal-down='disabled' 40 | session-switch-to-terminal-left='disabled' 41 | session-switch-to-terminal-right='disabled' 42 | session-switch-to-terminal-up='disabled' 43 | terminal-close='disabled' 44 | terminal-find='disabled' 45 | terminal-find-next='disabled' 46 | terminal-find-previous='disabled' 47 | terminal-maximize='disabled' 48 | terminal-select-bookmark='disabled' 49 | terminal-toggle-margin='disabled' 50 | win-reorder-next-session='disabled' 51 | win-reorder-previous-session='disabled' 52 | win-switch-to-next-session='disabled' 53 | win-switch-to-previous-session='disabled' 54 | win-switch-to-session-0='disabled' 55 | win-switch-to-session-1='disabled' 56 | win-switch-to-session-2='disabled' 57 | win-switch-to-session-3='disabled' 58 | win-switch-to-session-4='disabled' 59 | win-switch-to-session-5='disabled' 60 | win-switch-to-session-6='disabled' 61 | win-switch-to-session-7='disabled' 62 | win-switch-to-session-8='disabled' 63 | win-switch-to-session-9='disabled' 64 | win-view-sidebar='disabled' 65 | 66 | [profiles/2b7c4080-0ddd-46c5-8f23-563fd3ba789d] 67 | visible-name='Default' 68 | -------------------------------------------------------------------------------- /.tmux.conf: -------------------------------------------------------------------------------- 1 | # To reload this config file into running tmux sessions: 2 | # C-a :source-file ~/.tmux.conf 3 | 4 | # Use C-a as prefix (GNU screen muscle memory) 5 | unbind-key C-b 6 | set-option -g prefix C-a 7 | 8 | # "C-a a" to send a real C-a (i.e.: jumping to the line beginning) 9 | bind-key a send-prefix 10 | 11 | # C-a C-a to switch between two tabs 12 | bind-key C-a last-window 13 | 14 | # True color support https://github.com/tmux/tmux/issues/696 15 | set-option -ga terminal-overrides ",xterm-256color:Tc" 16 | 17 | # <>< 18 | set-option -g default-shell /usr/bin/fish 19 | 20 | # Count windows and panes from 1 21 | set-option -g base-index 1 22 | set-window-option -g pane-base-index 1 23 | 24 | # Customize status bar 25 | # https://arcolinux.com/everything-you-need-to-know-about-tmux-status-bar/ 26 | # Defaults (tmux show-options -g | grep status): 27 | # status on 28 | # status-interval 15 29 | # status-justify left 30 | # status-keys emacs 31 | # status-left "[#S] " 32 | # status-left-length 10 33 | # status-left-style default 34 | # status-position bottom 35 | # status-right " \"#{=21:pane_title}\" %H:%M %d-%b-%y" 36 | # status-right-length 40 37 | # status-right-style default 38 | # status-style fg=black,bg=green 39 | set-option -g status-position top 40 | set-option -g status-left '' 41 | set-option -g status-justify right 42 | set-option -g status-right '|#S' 43 | set-option -g status-bg '#117acc' # Same as VS Code Status Bar 44 | set-option -g status-fg '#ffffff' # Same as VS Code Status Bar 45 | 46 | # Comparison to screen: https://hyperpolyglot.org/multiplexers 47 | 48 | ### Misc 49 | # List sessions: tmux ls 50 | # New session foo: tmux new -s foo 51 | # Attach to session foo: tmux attach -t foo 52 | # Kill session foo: tmux kill-session -t foo 53 | # Kill tmux server: tmux kill-server 54 | # Detach from current session: C-a :detach 55 | # Detach session foo: tmux a -dt foo 56 | # Rename a session: C-a :rename-session 57 | # Rename a tab/window: C-a :rename-window 58 | # Change tag number from 2 to 3: C-a :move-window -s 2 -t 3 59 | # Renumber all tabs to be gapless: C-a :move-window -r 60 | # Show all settings: tmux show -g (or -s) 61 | 62 | ### Panes and tabs 63 | # Open a vertical pane: C-a + | 64 | # Open a horizontal pane: C-a + – 65 | # Move between panes: C-a + direction arrows 66 | # Drag a pane around: hold C-a and use direction arrows 67 | # Open a new tab: C-a + c 68 | # Move between tabs: C-a and use direction arrows 69 | # Close a tab: C-a :kill-window 70 | -------------------------------------------------------------------------------- /.vim/syntax/txt2tags.vim: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aureliojargas/dotfiles/f35732301f6592415f45038c00207c7e463b685e/.vim/syntax/txt2tags.vim -------------------------------------------------------------------------------- /.vimrc: -------------------------------------------------------------------------------- 1 | " Colors, please 2 | syntax on 3 | 4 | " Look mom, we're in the future! 5 | set encoding=utf-8 6 | 7 | " Fix common typos 8 | cabbrev W w 9 | cabbrev Q q 10 | cabbrev Wq wq 11 | cabbrev WQ wq 12 | 13 | " No beeps, just flashes 14 | set visualbell 15 | 16 | " Show line/column number at bottom 17 | set ruler 18 | 19 | " Show status line at bottom (and "file has changed" indicator) 20 | set laststatus=2 21 | 22 | " Options for a better search experience 23 | set incsearch hlsearch ignorecase smartcase magic 24 | 25 | " Colors for highlighted text (use NONE for transparent) 26 | highlight Visual ctermbg=Blue ctermfg=White 27 | highlight MatchParen ctermbg=Blue ctermfg=White 28 | highlight Search ctermbg=Yellow ctermfg=Black 29 | 30 | " Make tab completion more bash-like 31 | set wildmode=longest,list:full 32 | 33 | " My (current) code preferences 34 | set autoindent expandtab tabstop=4 35 | 36 | " Commands '>' and '<' will walk $tabstop spaces 37 | set shiftwidth=0 38 | 39 | " Append one, not two spaces after periods in 'gq' 40 | set nojoinspaces 41 | 42 | " Remember cursor position, save command history 43 | set viminfo='10,\"30,:40,%,n~/.viminfo 44 | autocmd BufReadPost * if line("'\"")|execute("normal `\"")|endif 45 | 46 | " Highlight tabs and trailing spaces 47 | " Sample: foo bar 48 | set list listchars=tab:\|·,trail:█,precedes:<,extends:> 49 | 50 | " Autosave, always 51 | autocmd TextChanged,TextChangedI * silent write 52 | 53 | " YAML defaults 54 | autocmd FileType yaml set tabstop=2 55 | 56 | " HTML/CSS defaults 57 | autocmd FileType html,css set tabstop=2 textwidth=0 58 | 59 | " Bash defaults 60 | autocmd FileType sh set textwidth=100 61 | 62 | " Python defaults 63 | autocmd FileType python set textwidth=88 " black default 64 | autocmd FileType python hi pythonString ctermfg=lightgreen 65 | autocmd FileType python hi pythonRawString ctermfg=lightgreen 66 | 67 | " txt2tags 68 | autocmd BufNewFile,BufRead *.t2t set filetype=txt2tags 69 | 70 | " Funções ZZ (tab-indented) 71 | autocmd BufNewFile,BufRead */funcoeszz/{*.sh,testador/run} 72 | \ set noexpandtab textwidth=72 73 | 74 | " Git commit message limited to 72 columns 75 | autocmd FileType gitcommit set textwidth=72 76 | 77 | " Quickly move betwwen open tabs 78 | nnoremap H :tabprevious 79 | nnoremap L :tabnext 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dotfiles 2 | 3 | My config files. 4 | 5 | I try to be a minimalist. No config fireworks here. 6 | 7 | I use the Fish shell. See `.config/fish/`. 8 | 9 | ## Setup a new machine 10 | 11 | $ fish setup 12 | -------------------------------------------------------------------------------- /bin/git-check-author: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | # List commits from all branches whose author email is not $email 3 | 4 | set email $argv[1] 5 | 6 | if test -z $email 7 | echo Missing argument: email 8 | exit 1 9 | end 10 | 11 | set branches (git branch -a | grep -v ' -> ' | cut -c3-) # local+remote 12 | 13 | for branch in $branches 14 | echo "$branch:" 15 | git log --oneline --date=short --pretty=format:"%h %ad [%ae] %s" $branch | 16 | grep -v -F "[$email]" 17 | end 18 | -------------------------------------------------------------------------------- /bin/git-cleanup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | 3 | # Remove remote-tracking references that no longer exist on the remote 4 | git fetch --prune 5 | 6 | # Remove local branches that were already merged 7 | git branch --merged | 8 | 9 | # Always keep the current branch 10 | grep -v '^\* ' | 11 | 12 | # Remove left padding 13 | cut -c 3- | 14 | 15 | # Always keep these branches 16 | grep -v -x -e main -e master -e gh-pages | 17 | 18 | while read branch 19 | git branch --delete $branch 20 | end 21 | -------------------------------------------------------------------------------- /bin/git-pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | # To be run before attempting to create a new commit. 3 | # The idea here is avoiding committing personal stuff with work 4 | # credentials and vice versa. 5 | 6 | set author (git config user.email) 7 | set origin (git remote get-url origin) 8 | 9 | set perso_author "verde@" 10 | set perso_origin "github.com?aureliojargas/" 11 | # In previous line, ? matches / (https) and : (ssh) 12 | 13 | set is_perso_author (string match -- "*$perso_author*" $author) 14 | set is_perso_origin (string match -- "*$perso_origin*" $origin) 15 | 16 | # If author or repo are personal, both must be personal 17 | if test -n "$is_perso_author" 18 | and test -n "$is_perso_origin" 19 | : 20 | else if test -n "$is_perso_author" 21 | or test -n "$is_perso_origin" 22 | echo "WARNING: using $author in $origin. Is that ok?" 23 | read 24 | end 25 | -------------------------------------------------------------------------------- /bin/git-pull-forced: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | 3 | # https://stackoverflow.com/a/5737794 4 | set dirty_tree (git status --porcelain) 5 | 6 | test -n "$dirty_tree"; and git stash --include-untracked 7 | 8 | # I'm assuming 10 commits ago the local copy sync'ed with the remote 9 | git fetch 10 | git reset --hard HEAD~10 11 | git merge FETCH_HEAD 12 | 13 | test -n "$dirty_tree"; and git stash pop 14 | -------------------------------------------------------------------------------- /bin/git-replace-text: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | # Get replace at https://github.com/aureliojargas/replace 3 | 4 | function usage 5 | set -l script_name (basename (status -f)) 6 | echo "Usage: $script_name pattern replacement [--regex]" 7 | end 8 | 9 | switch (count $argv) 10 | case 2 11 | set match_mode -F 12 | case 3 13 | set match_mode -P 14 | set regex_mode --regex 15 | case '*' 16 | usage 17 | exit 1 18 | end 19 | 20 | git grep $match_mode -I -l $argv[1] | while read path 21 | replace -i -f $argv[1] -t $argv[2] $regex_mode $path 22 | end 23 | -------------------------------------------------------------------------------- /setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | # 3 | # Setup all the dotfiles into a new machine. 4 | # May be run multiple times in the same machine. 5 | # Usage: ./setup 6 | # 7 | # GitHub Codespaces: 8 | # - This script is executed automatically in the container startup 9 | # - Logs: /workspaces/.codespaces/.persistedshare/creation.log 10 | 11 | # You should always run this script from the repository root 12 | set dotfiles_dir $PWD 13 | 14 | set user_bin $HOME/bin 15 | 16 | set fancy_diff_url https://github.com/so-fancy/diff-so-fancy/releases/download/v1.4.4/diff-so-fancy 17 | set replace_url https://raw.githubusercontent.com/aureliojargas/replace/main/replace.py 18 | 19 | function is_termux 20 | string match -q '*termux*' $HOME 21 | end 22 | 23 | function is_macos 24 | test (uname) = Darwin 25 | end 26 | 27 | function is_linux 28 | test -f /etc/debian_version 29 | and not is_github_codespace 30 | and not is_chromeos 31 | and not is_android 32 | end 33 | 34 | function is_chromeos 35 | test (uname -n) = penguin 36 | end 37 | 38 | function is_github_codespace 39 | test "$CODESPACES" = true 40 | end 41 | 42 | function is_android # avalible since Android 15 43 | test "$USER" = droid 44 | end 45 | 46 | function log 47 | set_color cyan 48 | echo "[$argv]" 49 | set_color normal 50 | end 51 | 52 | function done 53 | if set -q argv[1] 54 | set message $argv 55 | else 56 | set message Done 57 | end 58 | set_color green 59 | echo ✓ $message 60 | set_color normal 61 | end 62 | 63 | function already_done 64 | if set -q argv[1] 65 | set message $argv 66 | else 67 | set message Already done 68 | end 69 | set_color yellow 70 | echo ✓ $message 71 | set_color normal 72 | end 73 | 74 | function failed 75 | if set -q argv[1] 76 | set message $argv 77 | else 78 | set message Failed 79 | end 80 | set_color red 81 | echo × $message 82 | set_color normal 83 | end 84 | 85 | function run 86 | set fish_trace 1 87 | $argv 88 | set fish_trace 0 89 | end 90 | 91 | function download 92 | run curl --location --silent --output $argv[2] $argv[1] 93 | end 94 | 95 | function apt_install 96 | run sudo apt --yes --quiet --quiet install --no-install-recommends $argv 97 | end 98 | 99 | function brew_install 100 | run brew install --quiet $argv 101 | end 102 | 103 | function pipx_install 104 | run pipx install $argv 105 | end 106 | 107 | function create_symlink -a target symlink 108 | if not test (count $argv) -eq 2 109 | echo ERROR: usage: create_symlink target symlink 110 | exit 1 111 | end 112 | 113 | # Make sure the symlink parent dir exists 114 | mkdir -p (dirname $symlink) 115 | 116 | if test -L $symlink 117 | if test (readlink $symlink) = $target 118 | already_done $symlink is already done 119 | else 120 | failed $symlink is a symlink to an unknown file 121 | end 122 | else if test -f $symlink 123 | failed $symlink is a normal file, skipping symlink creation 124 | else if test -d $symlink 125 | failed $symlink is a directory, skipping symlink creation 126 | else 127 | ln -s -v $target $symlink 128 | end 129 | end 130 | 131 | #----------------------------------------------------------------------- 132 | 133 | log 'Detecting the platform' 134 | is_termux; and echo Termux 135 | is_macos; and echo macOS 136 | is_linux; and echo Linux 137 | is_chromeos; and echo ChromeOS '(Crostini)' 138 | is_github_codespace; and echo GitHub Codespace 139 | is_android; and echo Android native Terminal.app 140 | 141 | if is_github_codespace 142 | # This repo is always cloned in every codespace because I've enabled 143 | # it in my user configuration: Automatically install dotfiles 144 | set dotfiles_dir /workspaces/.codespaces/.persistedshare/dotfiles 145 | end 146 | 147 | set paths \ 148 | .aliases \ 149 | .config/fish/conf.d/my.fish \ 150 | .config/fish/functions/brew_activate.fish \ 151 | .config/fish/functions/fish_prompt.fish \ 152 | .config/micro/bindings.json \ 153 | .config/micro/plug \ 154 | .config/micro/settings.json \ 155 | .gemrc \ 156 | .gitconfig \ 157 | .inputrc \ 158 | .screenrc \ 159 | .tmux.conf \ 160 | .vim \ 161 | .vimrc 162 | 163 | if is_termux 164 | set paths $paths \ 165 | .config/fish/conf.d/my.termux.fish \ 166 | .termux 167 | end 168 | 169 | if is_macos 170 | set paths $paths \ 171 | .config/fish/conf.d/my.osx.fish 172 | end 173 | 174 | log 'Create symlinks' 175 | for path_ in $paths 176 | create_symlink $dotfiles_dir/$path_ $HOME/$path_ 177 | end 178 | 179 | if is_chromeos 180 | rm $HOME/.config/micro/bindings.json 181 | create_symlink \ 182 | $dotfiles_dir/.config/micro/bindings.chromeos.json \ 183 | $HOME/.config/micro/bindings.json 184 | end 185 | 186 | log 'Create user bin directory' 187 | if test -d $user_bin 188 | already_done 189 | else 190 | mkdir -p -v $user_bin 191 | end 192 | 193 | log 'Install my scripts in ~/bin' 194 | for path_ in bin/* 195 | create_symlink $dotfiles_dir/$path_ $HOME/$path_ 196 | end 197 | 198 | log 'Install diff-so-fancy in user bin' 199 | set fancy_diff $user_bin/diff-so-fancy 200 | if test -f $fancy_diff; and test -x $fancy_diff 201 | already_done 202 | else 203 | download $fancy_diff_url $fancy_diff 204 | chmod +x $fancy_diff 205 | done Installed $fancy_diff 206 | end 207 | 208 | log 'Install the replace script' 209 | set replace_path $user_bin/replace 210 | if command --search --quiet replace 211 | already_done 212 | else 213 | download $replace_url $replace_path 214 | chmod +x $replace_path 215 | done Installed $replace_path 216 | end 217 | 218 | log 'Install extra software' 219 | if is_macos 220 | brew_install coreutils ed fish git-delta git-revise gnu-sed micro notunes pipx python shellcheck tig tree uv wget 221 | 222 | else if is_termux 223 | pkg update 224 | pkg install file fish git git-delta make openssh python tig tree vim 225 | pkg install termux-api # for pbcopy/pbpaste aliases 226 | python -m pip install --user pipx 227 | pipx_install git-revise uv 228 | 229 | log 'Some optional installs you may want to do' 230 | echo 'pkg install lynx # funcoeszz' 231 | echo 'pkg install perl # diff-so-fancy' 232 | echo 'pkg install ruby # Jekyll' 233 | 234 | else if is_linux 235 | sudo apt --quiet --quiet update 236 | apt_install ed make python3-pip tig tree 237 | brew_install fish git-delta git-revise micro pipx shellcheck uv 238 | 239 | log 'Some optional installs you may want to do' 240 | echo 'sudo apt install lynx # funcoeszz' 241 | echo 'sudo apt install ruby # Jekyll' 242 | 243 | else if is_android 244 | # brew is not supported on ARM64 245 | # https://docs.brew.sh/Homebrew-on-Linux#arm-unsupported 246 | 247 | sudo apt --quiet --quiet update 248 | apt_install ed fish make pipx python3-pip shellcheck tig tree 249 | pipx_install git-revise uv 250 | 251 | log 'Installing git-delta from GitHub' 252 | if command -v delta 253 | already_done 254 | else 255 | set delta_arm64_url https://github.com/dandavison/delta/releases/download/0.18.2/git-delta_0.18.2_arm64.deb 256 | download $delta_arm64_url /tmp/delta.deb 257 | run sudo dpkg -i /tmp/delta.deb 258 | done 259 | end 260 | 261 | log 'Installing micro from GitHub' 262 | if command -v micro 263 | already_done 264 | else 265 | set micro_version 2.0.14 266 | set micro_arm64_url https://github.com/zyedidia/micro/releases/download/v$micro_version/micro-$micro_version-linux-arm64.tgz 267 | download $micro_arm64_url /tmp/micro.tgz 268 | tar -C /tmp -xzf /tmp/micro.tgz 269 | cp /tmp/micro-$micro_version/micro ~/bin 270 | done Installed ~/bin/micro 271 | end 272 | 273 | log 'Some optional installs you may want to do' 274 | echo 'sudo apt install ruby # Jekyll' 275 | 276 | else if is_github_codespace 277 | # Install the missing software just in case we're running on the 278 | # default GitHub Codespaces container. This is a noop in my personal 279 | # devcontainer (https://github.com/aureliojargas/devcontainer) since 280 | # those are already installed there. 281 | sudo apt --quiet --quiet update 282 | apt_install ed git-revise shellcheck tig tree 283 | end 284 | 285 | if is_macos 286 | log "macOS: Add 'ed' symlink so GNU ed can be used as git core.editor" 287 | create_symlink /opt/homebrew/opt/ed/bin/ged $HOME/bin/ed 288 | end 289 | 290 | log 'Setup Git local configuration (manual intervention needed)' 291 | set git_config_local $HOME/.gitconfig.personal 292 | if is_github_codespace 293 | # GitHub Codespace sets GIT_COMMITTER_EMAIL and GIT_COMMITTER_NAME 294 | # env variables to the correct values 295 | already_done 'Skipped, git email/name already set by env vars.' 296 | else if test -f $git_config_local 297 | already_done 298 | else 299 | cp -v $dotfiles_dir/.gitconfig.local.template $git_config_local 300 | echo Please edit $git_config_local 301 | end 302 | 303 | # vim: filetype=fish 304 | --------------------------------------------------------------------------------