• Cross-platform vim-plug setup

    I’ve recently switch to vim-plug, a lightweight Vim plugin manager.

    It comes with a little .vimrc snippet which downloads the plugin, but it only works for Unix. I use Vim across all three platforms regularly, so I updated the snippet:

    " Download and install vim-plug (cross platform).
    if empty(glob(
        \ '$HOME/' . (has('win32') ? 'vimfiles' : '.vim') . '/autoload/plug.vim'))
      execute '!curl -fLo ' .
        \ (has('win32') ? '\%USERPROFILE\%/vimfiles' : '$HOME/.vim') . 
        \ '/autoload/plug.vim --create-dirs ' .
        \ 'https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
      autocmd VimEnter * PlugInstall --sync | source $MYVIMRC
    endif
    

    The above should work across all three major OSes, since Windows 10 recently received curl support.

  • Minimalist phone launcher

    For the past few years I’ve been trying to focus on having more mindful experiences in my life. I find it rewarding to be present in the moment, without my thoughts rushing onto whatever awaits me next.

    I present to you the biggest distraction: my phone.

    I use to get in touch with people I love. I’m more productive at work because I have access to information on the go. I also use my phone to browse Reddit, YouTube, and every other media outlet imaginable. Even worse, sometimes I just waste time tinkering with the settings or mindlessly browsing through the apps I have installed.

    It’s an attention sink.

    Nearly a year ago as I was browsing the Google Play Store I bumped into a new launcher: KISS. The tag line caught my attention: “Keep It Simple, Stupid”. I went ahead and downloaded the launcher. I haven’t changed to another launcher since.

    Here’s how my home screen looks today:

    A screenshot of a KISS launcher home screen.

    There’s nothing besides a single search bar. The search bar takes me to the apps I need, web searches I’m interested in, or people I’m trying to reach out to.

    It’s simple to use. Start typing an app or a contact name, and the results show up above the search bar:

    A screenshot of searching for an app with a KISS launcher.

    This simple concept has been responsible for cutting hours upon hours from my phone usage. Opening an app becomes a more deliberate experience, I open my phone with a purpose (granted this purpose might be to kill hours looking at cat videos). There’s no more scrolling through everything I have installed just to find something to stimulate my attention for a few more seconds.

    You can download the KISS Launcher for Android from Google Play Store.

  • Making a packing spreadsheet

    Being the unique person I am, I love traveling. Oftentimes I end up getting deep into trying to optimize my packing methods. There are dozens of tools online designed to help with this exact thing (services like Lighterpack or GearGrams). But, being handy with code, I decided to dabble in the subject on my own.

    One of the most important things in packing is the overall weight of the pack, and I always want to know what type of things are the heaviest. I also want to be able to run random queries on my data, whatever it is that I’m trying to learn. I want to have an inventory of items (backpacks, clothes, storage solutions) which I can plug in and out of a spreadsheet to see how the resulting picture changes on the go. Working with software as my day job, I’d also like for the solution to be automated whenever possible.

    Google Spreadsheets turned out to be the perfect solution, providing the ability to quickly sum up the weight of my things and output insights about the data.

    Final Result

    Here’s a link to the spreadsheet, I encourage the reader to copy and play around with in anyway you see fit.

    Here’s the final result for a multi-day trip I will have for this year. As you can see, my pack weighs around 3 kilograms or a bit over 6 freedom units (not including water). My recently purchased Nintendo Switch is the heaviest item (and it’s worth every gram as it makes flying tolerable), but clothes take up most of the weight:

    A screenshot of a packing spreadsheet.

    I use indentation levels to show that some items are contained within other items. This also lets me calculate the absolute and relative weights of a whole container with everything inside of it (see fields labeled “Container” and “Percentage”).

    The “Weight” and the “Breakdown” fields indicate absolute and relative item weight respectively, which accounts for quantity of the item (quantity defaults to 1 if not explicitly set). Weight-related fields are color coded from lightest items in green to heaviest items in red.

    Categories are used to group items and execute queries on the data and learn insights like these:

    A chart representing the weight of each category of items relative to a weight of the whole pack.

    There’s a separate sheet where I enter item names, categories, and weights, which I then use to automatically populate values above and autofill item names in the primary sheet:

    A screenshot of an inventory tab of the packing spreadsheet.

    The Technical Details

    I haven’t worked with Google Spreadsheets (or Excel for that matter) a lot in the past, but with an access to clear documentation (and a hundred of web searches later) it was straightforward to get the hang of the it.

    I started off by manually populating the final result sheet, manually adjusting formulas for Container/Percentage cells, as I had no idea how I would detect the indentation yet. I like when things look pretty, so I applied conditional formatting rules right away, and the looks of the sheet really haven’t changed throughout the process.

    Next, I created an inventory sheet, which I wanted to use as a source of data in the resulting sheet. A few Google searches and some trial & error resulted in a lookup formula:

    =ArrayFormula(
     IF(
       NOT(ISBLANK($B2)),
       INDEX(InventoryCategories, MATCH($B2, InventoryItems, 0)),
       IF(
         NOT(ISBLANK($C2)),
         INDEX(InventoryCategories, MATCH($C2, InventoryItems, 0)),
         INDEX(InventoryCategories, MATCH($D2, InventoryItems, 0))
       )
      )
    )
    

    ArrayFormula is necessary in order to create arrays on the fly without printing intermediate results in the spreadsheet. InventoryItems and InventoryCategories are named ranges referring to item names and categories in the inventory sheet. MATCH finds an index of a first occurrence of the item name in the sheet, and retrieves corresponding category name. An item weight is retrieved by the exact same principle.

    Trying to find the container weight took a lot more time, and resulted in a lot more headache. Solution for handling indentation in Google Spreadsheets wasn’t as elegant as I would have hoped for, but it got the job done:

    =ArrayFormula(
         SUM(
           $I2:INDIRECT(
             CONCATENATE(
               "$I",
               ROW() + IF(
                 NOT(ISBLANK($B2)),
                 MATCH(FALSE, ISBLANK($B3:$B), 0),
                 MATCH(FALSE, ISBLANK($C3:$C), 0)
               ) - 1
             )
           )
         )
    

    The formula above finds the first non-empty cell in a column. It starts searching from the next row (for example, for an item on a second row, we look at the third row and below). After it knows which row first non-empty cell is in, the formula turns it into a string (column $I contains item weights) and use it as an upper bound of a sum of the weights. Finished formula is a bit longer (it adds sugar to only display the number when needed), and if you’re interested - you can look it up in the spreadsheet.

    A screenshot used for explaining indentation logic.

    For example, in the screenshot above, the formula will start looking at cells in a column right after the “Packing cube”. As soon as it finds a non-empty cell (“Nintendo Switch case”), the formula will determine this row to be an upper boundary. The formula then will sum weights starting with a “Packing cube” row, and up to but not including “Nintendo Switch case” row.

    The rest involved many small tweaks, adding pretty colors, hiding N/A errors and zeroes, and trying to find the perfect shade for column borders.

    And, since you made it this far, here’s how the numbers above look in the real world:

    Side by side pictures of a packed backpack: open and closed.

    Hopefully you found this useful, or at least somewhat entertaining. There’s a lot of room for improvement, and I aimed to provide a framework and a few basic ideas for building a spreadsheet to accommodate one’s twisted ultralight needs.

  • My .vimrc, annotated

    I’ve been using Vim for anywhere between 5 and 10 years, and my .vimrc is all grown up. I use {{{1 to annotate folds in my .vimrc, and use zM to close all folds and zR to open all folds (zc, zo, and za respectively close, open, and toggle individual folds). The --- lines are simply cosmetic.

    " => Pre-load ------------------------------------------------------------- {{{1
    
    set nocompatible  " Required for many plugins, ensures it's not compatible with 
                      " Vi, which nobody uses at this point.
    filetype plugin indent on  " Great answer: https://vi.stackexchange.com/a/10125
    

    Vundle is downright fantastic plugin manager. It allows one to install plugins with :PluginInstall and upgrade plugins with :PluginUpdate. Simple, easy, reliable. Of course you’ll need to have Vundle installed, which I normally have as a git submodule.

    " Required Vundle setup.
    set runtimepath+=~/.vim/bundle/vundle
    set runtimepath+=$GOROOT/misc/vim
    call vundle#rc()
    
    " => Vundle plugins ------------------------------------------------------- {{{1
    
    Plugin 'gmarik/vundle' " The Vundle itself, to keep itself updated.
    
    " Colorschemes:
    Plugin 'NLKNguyen/papercolor-theme'
    Plugin 'ajh17/Spacegray.vim.git'
    Plugin 'altercation/vim-colors-solarized'
    Plugin 'squarefrog/tomorrow-night.vim'
    Plugin 'vim-scripts/ScrollColors'  " Allows scrolling through colorschemes.
    
    " Language/tool integration and support:
    Plugin 'burnettk/vim-angular'
    Plugin 'fatih/vim-go'
    Plugin 'christoomey/vim-tmux-navigator'
    Plugin 'mileszs/ack.vim'
    Plugin 'motemen/git-vim'
    Plugin 'nvie/vim-flake8'
    Plugin 'pangloss/vim-javascript'
    Plugin 'scrooloose/syntastic.git'  " Syntax checker.
    Plugin 'tpope/vim-fugitive.git'    " Even better Git support.
    
    " Quality of life:
    Plugin 'EinfachToll/DidYouMean'    " For typos during opening files.
    Plugin 'ciaranm/detectindent'      " Automatically detect indent.
    Plugin 'ervandew/supertab'         " Smarter autocompletion.
    Plugin 'junegunn/goyo.vim'         " A plugin for writing prose.
    Plugin 'majutsushi/tagbar'         " List tags in a sidebar.
    Plugin 'scrooloose/nerdtree'       " A directory tree in a sidebar.
    Plugin 'tomtom/tcomment_vim'       " Easy comment blocks with <Leader>cc.
    Plugin 'tpope/vim-abolish'         " Extended abbreviation/substition.
    Plugin 'tpope/vim-repeat'          " Intelligent repeat with '.'
    Plugin 'tpope/vim-surround'        " Work with pairs of quotes/anything.
    Plugin 'tpope/vim-unimpaired.git'  " Handy bracket mappings.
    Plugin 'tpope/vim-vinegar'         " Enhanced directory browser.
    Plugin 'vim-scripts/DirDiff.vim'   " Directory level diff.
    
    " New features:
    Plugin 'Lokaltog/vim-easymotion'   " Easy navigation with <Leader><Leader>w.
    Plugin 'kien/ctrlp.vim'			   " Hit <C>p for a list of files/buffers.
    Plugin 'vim-scripts/Gundo.git'     " Intelligent undo tree.
    Plugin 'vim-scripts/vimwiki'       " A personal local Wiki.
    
    if v:version > 703
      Plugin 'SirVer/ultisnips'        " Intricate snippets.
      Plugin 'chrisbra/vim-diff-enhanced'
    endif
    
    " => Plugins configuration ------------------------------------------------ {{{1
    
    " NERDTree: auto close if last window.
    function! s:CloseIfOnlyNerdTreeLeft()
      if exists("t:NERDTreeBufName")
        if bufwinnr(t:NERDTreeBufName) != -1
          if winnr("$") == 1
            q
          endif
        endif
      endif
    endfunction
    
    " Force Gundo preview to the bottom.
    let g:gundo_preview_bottom = 1
    
    " Map Gundo.
    nnoremap <F5> :GundoToggle<cr>
    
    " DetectIndent: Enable and configure.
    augroup detectindent
      autocmd!
      autocmd BufReadPost * :DetectIndent
    augroup END
    let g:detectindent_preferred_expandtab = 1
    let g:detectindent_preferred_indent = 2
    
    " UltiSnips: Compatibility with YouCompleteMe via SuperTab.
    let g:ycm_key_list_select_completion = ['<C-n>', '<Down>']
    let g:ycm_key_list_previous_completion = ['<C-p>', '<Up>']
    let g:SuperTabDefaultCompletionType = '<C-n>'
    let g:UltiSnipsExpandTrigger = "<tab>"
    let g:UltiSnipsJumpForwardTrigger = "<tab>"
    let g:UltiSnipsJumpBackwardTrigger = "<s-tab>"
    
    " VimWiki: default location.
    let g:vimwiki_list = [{
      \ 'path': '$HOME/Dropbox/wiki',
      \ 'template_path': '$HOME/Dropbox/wiki/templates',
      \ 'template_default': 'default',
      \ 'template_ext': '.html'}]
    
    " Map Tagbar.
    nnoremap <F8> :TagbarToggle<cr>
    
    " Synastic configuration.
    let g:syntastic_always_populate_loc_list = 1  " Make :lnext work.
    let g:syntastic_html_checkers = ['']
    let g:syntastic_javascript_checkers = ['gjslint', 'jshint']
    let g:syntastic_javascript_gjslint_args = '--strict'
    let g:syntastic_python_checkers = ['gpylint']
    

    Most plugins above change slightly change daily Vim workflow: the way one navigates files, replaying actions, working with snippets, minor tweaks to editing - and I highly recommend at least skimming through README of plugins you’re interested in so you can incorporate the changes in your workflow.

    I have a set of simple defaults I use everywhere, major changes being changing : to ; and moving my leader key to a spacebar. Everything else are tiny quality of life tweaks.

    " => Editing -------------------------------------------------------------- {{{1
    
    syntax on
    
    " Indentation settings.
    set autoindent
    set expandtab
    set shiftwidth=4
    set softtabstop=4
    set tabstop=4
    
    " Disable backups and .swp files.
    set nobackup
    set noswapfile
    set nowritebackup
    
    " Semicolon is too long to type.
    nnoremap ; :
    vnoremap ; :
    
    " Map leader key.
    let mapleader = "\<Space>"
    
    " Use system clipboard.
    set clipboard=unnamedplus
    
    " Enable wild menu (tab command autocompletion).
    set wildmenu
    set wildmode=list:longest,full
    
    " Don't complain about unsaved files when switching buffers.
    set hidden
    
    " Make soft line breaks much better looking.
    if v:version > 703
      set breakindent
    endif
    
    " Pretty soft break character.
    let &showbreak='↳ '
    
    " => Looks ---------------------------------------------------------------- {{{1
    
    set background=dark
    colorscheme spacegray
    
    " Set terminal window title and set it back on exit.
    set title
    let &titleold = getcwd()
    
    " Shorten press ENTER to continue messages.
    set shortmess=atI
    
    " Show last command.
    set showcmd
    
    " Highlight cursor line.
    set cursorline
    
    " Ruler (line, column and % at the right bottom).
    set ruler
    
    " Display line numbers if terminal is wide enough.
    if &co > 80
      set number
    endif
    
    " Soft word wrap.
    set linebreak
    
    " Prettier display of long lines of text.
    set display+=lastline
    
    " Always show statusline.
    set laststatus=2
    
    " => Movement and search -------------------------------------------------- {{{1
    
    " Ignore case when searching.
    set ignorecase
    set smartcase
    
    " Fast split navigation.
    nnoremap <C-j> <C-W><C-J>
    nnoremap <C-k> <C-W><C-K>
    nnoremap <C-l> <C-W><C-L>
    nnoremap <C-h> <C-W><C-H>
    
    " Absolute movement for word-wrapped lines.
    nnoremap j gj
    nnoremap k gk
    
    " => Misc ----------------------------------------------------------------- {{{1
    
    " Use Unix as the standart file type.
    set ffs=unix,dos,mac
    
    " Ignore compiled files.
    set wildignore=*.o,*~,*.pyc,*.pyo
    
    " Ignore virtualenv directory.
    set wildignore+=env
    
    " Fold using {{{n, where n is fold level
    set foldmethod=marker
    
    " => Fixes and hacks ------------------------------------------------------ {{{1
    
    " Ignore mouse (in GVIM).
    set mouse=c
    
    " Fix backspace behavior in GVIM.
    set bs=2
    
    " NERDTree arrows in Windows.
    if has("win32") || has("win64") || has("win32unix")
      let g:NERDTreeDirArrows = 0
    endif
    
    " Increase lower status bar height in diff mode.
    if &diff
      set cmdheight=2
    endif
    
    " Unfold all files by default.
    au BufRead * normal zR
    

    I have some custom commands and shortcuts I’m using, but not too many. I find that I mostly just forget to use shortcuts I make, and I end up deleting lines from this section regularly.

    " => Custom commands ------------------------------------------------------ {{{1
    
    " Trim trailing whitespace in the file.
    command TrimWhitespace %s/\s\+$//e
    
    " Command to close current buffer without closing the window.
    command Bd :bp | :sp | :bn | :bd
    
    " => Leader shortcuts ----------------------------------------------------- {{{1
    
    nnoremap <Leader>] <C-]>          " Jump to ctags tag definition.
    nnoremap <Leader>p :CtrlP<cr>     " Fuzzy complete for files.
    nnoremap <Leader>t :CtrlPTag<cr>  " Fuzzy complete for tags.
    nnoremap <Leader>r :redraw!<cr>   " Redraw the screen (for visual glitches).
    nnoremap <Leader>w :w<cr>         " Write a file.
    

    Hope you find this useful and take away a few bits and pieces for your own workflow.

  • Automating Octorpress publishing

    I really like Octopress. It builds on top of Jekyll to address certain rough edges, and provides ready to go lighting fast blogging platform. It’s easily extendible if you know how to code (thanks to a rather clean and well organized code base), and posts are just plain Markdown files.

    One issue though - I need to be near a computer to publish and preview the article. This becomes difficult if I’m traveling with, say, a tablet.

    I have a low end AWS Lightsail instance I use for writing and publishing, but it’s not always fun to write when SSHing into a server, and I often write offline - making it even more difficult to move files between where I write and where I publish. And managing images is a nightmare. To resolve this, I set up a few directories I use in Dropbox, and wrote a few scripts to move things around when needed.

    Here’s a directory structure in Dropbox:

    - blog/
      - posts/
        - 2017-11-20-automatic-octopress-publishing.markdown
      - img/
        - input/
          - a-picture-of-a-flower.jpg
        - output/
    

    I put Markdown files in Dropbox/blog/posts/ (and numerous offline editors sync with Dropbox - I’m writing this with StackEdit, and I use iA Writer on my tablet). I add my images to Dropbox/img/input/. I tend to strip metadata from my images and resize them to fit the maximum page width (I don’t really care for high resolution pictures, speed is preferred over ability to zoom into a picture). For this, two tools are needed:

    sudo apt-get install imagemagick exiftool
    

    When I’m done writing or want to preview an article, I SSH into my AWS Lightsail instance and run sync.sh, a small script which moves posts to a proper directory, processes images and places them in the desired location, as well as starts Octopress instance (this way I can preview my blog on the AWS Lightsail instance). Contents of sync.sh (don’t forget to chmod +x):

    #!/bin/bash
    cd $HOME/Dropbox/blog/img/input
    mogrify -resize 832x620 -quality 100 -path $HOME/Dropbox/blog/img/output *.jpg
    exiftool -all= $HOME/Dropbox/blog/img/output/*.jpg
    cp $HOME/Dropbox/blog/posts/*.markdown $HOME/blog/source/_posts
    cp $HOME/Dropbox/blog/img/output/*.jpg $HOME/blog/source/images/posts
    cd $HOME/blog
    rake generate
    rake preview
    

    I run the above script every time I want to preview the site. I’m sure it’s easy to set up a daemon to watch for changes in the Dropbox folders, but I don’t see the need for that yet. Also, I just statically resize images to a particular resolution (832x620) since all of the pictures I upload have the same aspect ratio, I’m sure there’s a way to calculate that number on the fly for this script to work with different aspect ratios.

    Lastly, when I deployed and committed my changes (still commit and deploy manually out of a habit), I run archive.sh:

    #!/bin/bash
    mv $HOME/Dropbox/blog/posts/*.markdown $HOME/Dropbox/blog/published
    rm $HOME/Dropbox/blog/img/input/*
    rm $HOME/Dropbox/blog/img/output/*
    

    It’s not much, but enough to save some manual labor involved in publishing to Octopress.