vim 配置文件更新

时隔多年,趁着这次折腾 Golang 开发环境的机会,把 N 多年没有更新的 vim 配置文件重新改了一版。好久没使用,发现很多东西都记不清楚了,再加上这几年 vim 的插件管理也变化挺大,花了点时间学习,不过总是越来越方便了。

下面是我的 vim 配置文件(Github 地址),主要针对 go 语言的开发环境。

" Modeline and Notes {
"   vim: set foldmarker={,} foldlevel=0:
"   
"   Note: This is my personal .vimrc, I don't recommend you copy it, just
"   use the pieces you want. 
"
"   Contact:
"   tuantuan 
"
""  Usage:
"   1. Install the plugin manager 'vim-plug':
"
"     # curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
"
"   2. Copy the vimrcfile to your user configure directory
"
"     # cp the_vimrc_file ~/.vimrc
"
"   2. Reopen vim and run :PlugInstall to install the plugins:
"
"     :PlugInstall
"
"   3. (MacOS required) Install the ctags binary:
"
"     # brew install ctags
"
"   4. (Deoplete required) Install the dependency for deoplete plugin:
"
"     # pip3 install --user pynvim
"
"   Last updated: 2019-12-13 12:00:00
" }

" Basics {
    set nocompatible    " explicitly get out of vi-compatible mode
    filetype plugin indent on   " load filetype plugins/indent settings
    syntax on           " syntax highlight on
    set termguicolors   " enable 24bit color

    set backspace=indent,eol,start    " make backspace a more flexible
    set clipboard+=unnamed   " share window clipboard
    set fileformats=unix,dos,mac " support all three, in this order

    let &keywordprg=':help'		      " program to use for the |K| command.  
    let mapleader=','

    " Encodings {
        set enc=utf-8		" Sets the character encoding used inside Vim.
        set fenc=utf-8	    " Sets the character encoding for the file.
        set fencs=ucs-bom,utf-8,gb18030,gbk,gb2312,cp936		
    " }

    " set backup    " make backup files
    " set backupdir=$HOME/.vim/backup " where to put backup files
    " set autochdir  " always switch to the current file directory
" }

" Text Formatting/Layout {
    set textwidth=79
    set completeopt=menu    " use a pop up menu for completions
    set wildmenu    " turn on command line complete wild style
    set wildignore=*.dll,*.bak,*.exe,*.pyc  " ignore these list file extension
    set formatoptions=tcrql

    " Indent {
        set autoindent
        set smartindent
        set cindent "  enable automatic C program indenting.  
    " }

    " Tab {
        set tabstop=4   " real tabs should be 4
        set shiftwidth=4    " auto-indent amount
        set expandtab   " no real tabs please!
        set smarttab 
    " }
" } 

" Plugins { 
    " Vim plugin manager start here
    call plug#begin('~/.vim/plugged')

    Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }
    Plug 'scrooloose/nerdtree'
    Plug 'majutsushi/tagbar'
    Plug 'vim-airline/vim-airline'
    Plug 'arcticicestudio/nord-vim'

    " Deoplete completion
    Plug 'Shougo/deoplete.nvim'
    Plug 'roxma/nvim-yarp'
    Plug 'roxma/vim-hug-neovim-rpc'

    " Initialize plugin system
    call plug#end()

    " Vim-go settings
    let g:go_fmt_command = "goimports"
    let g:go_list_type = "quickfix"
    let g:go_gopls_complete_unimported = 1
    let g:go_version_warning = 1
    let g:go_highlight_types = 1
    let g:go_highlight_fields = 1
    let g:go_highlight_functions = 1
    let g:go_highlight_function_calls = 1
    let g:go_highlight_operators = 1
    let g:go_highlight_extra_types = 1
    let g:go_highlight_methods = 1
    let g:go_highlight_generate_tags = 1
    let g:go_highlight_format_strings = 1

    " NERDTree settings
    nnoremap n :NERDTreeToggle

    let NERDTreeShowLineNumbers=1
    let NERDTreeAutoCenter=1
    let NERDTreeShowHidden=0
    let NERDTreeIgnore=['\.pyc','\~$','\.swp']
    let NERDTreeShowBookmarks=2
    let g:nerdtree_tabs_open_on_console_startup=1

    autocmd VimEnter * NERDTree | wincmd p
    autocmd StdinReadPre * let s:std_in=1
    autocmd VimEnter * if argc() == 1 && isdirectory(argv()[0]) && !exists("s:std_in") | exe 'NERDTree' argv()[0] | wincmd p | ene | exe 'cd '.argv()[0] | endif
    autocmd BufEnter * if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif

    " Tagbar settings
    nnoremap t :TagbarToggle
    autocmd FileType go nested :TagbarOpen

    let g:tagbar_compact = 1
    let g:tagbar_autoshowtag = 1
    let g:tagbar_type_go = {
        \ 'ctagstype' : 'go',
        \ 'kinds'     : [
            \ 'p:package',
            \ 'i:imports:1',
            \ 'c:constants',
            \ 'v:variables',
            \ 't:types',
            \ 'n:interfaces',
            \ 'w:fields',
            \ 'e:embedded',
            \ 'm:methods',
            \ 'r:constructor',
            \ 'f:functions'
        \ ],
        \ 'sro' : '.',
        \ 'kind2scope' : {
            \ 't' : 'ctype',
            \ 'n' : 'ntype'
        \ },
        \ 'scope2kind' : {
            \ 'ctype' : 't',
            \ 'ntype' : 'n'
        \ },
        \ 'ctagsbin'  : 'gotags',
        \ 'ctagsargs' : '-sort -silent'
    \ }

    " Deoplete.nvim settings
    inoremap   pumvisible() ? "\" : "\"
    inoremap   pumvisible() ? "\" : "\"
    inoremap   pumvisible() ? "\" : "\"

    let g:deoplete#enable_at_startup = 1
    if exists('g:deoplete#enable_at_startup')
        call deoplete#custom#option('omni_patterns', { 'go': '[^. *\t]\.\w*' })
    endif
" }

" UI {
    set number  " turn on line number
    set numberwidth=5   " we are good up to 99999 lines
    set report=0    " tell us when anything is changed via :...
    set ruler   " always show current positions along the bottom
    set showcmd " show the command being typed
    set showmode    " show editing mode
    set showmatch   " show matching brackets
    set matchpairs+=<:>
    set splitbelow " show split window below
    set splitright
    set completeopt=longest,menu    " use a pop up menu for completions

    " Highlight {
        "set cursorcolumn    " highlight the current column
        "highlight cursorcolumn guibg=lightblue

        set cursorline  " highlight the current line
        highlight cursorline cterm=bold
        "highlight linenr ctermfg=darkcyan
    " }

    " Colorscheme {
        set background=dark
        colorscheme nord
    " }

    " Search {
        set hlsearch    " highlight search result.
        set incsearch   " do search as you type your search phrase
        set ignorecase smartcase    " smart ignore case when searching.
    " }

    " Statusline {
        set laststatus=2    " always show the status line
        set statusline=
        set statusline+=%-3.3n                       " buffer number
        set statusline+=%t                           " filename
        set statusline+=%h%m%r%w                     " status flags
        set statusline+=[%{&ff}]                     " file format
        set statusline+=[%{strlen(&ft)?&ft:'none'}]  " file type
        set statusline+=%=                           " right align remainder
        set statusline+=0x%-8B                       " character value
        set statusline+=%-14(%l,%c%V%)               " line, character
        set statusline+=%<%LL                        " file lines
    " }
" }

" GUI {
    if has('gui_running')
        set lines=999 columns=999 " size of gvim window

        " Remove toolbar and menubar
        set guioptions-=T
        set guioptions-=m

        " Font. Very important.
        if has('win32') || has('win64')
            set guifont=Consolas:h11:cANSI
        elseif has('gui_macvim')
            set transparency=10
            set guifont=Monaco:h12
            "set showtabline=2

            " Remove scrollbar also
            set guioptions-=r
            set guioptions-=R
            set guioptions-=l
            set guioptions-=L
        elseif has('unix')
            set guifont=Monospace\ 12
        endif
    endif
" }

" User-defined {
    " Easy edit of files in the same directory.
    if has('unix')
        nnoremap ,e :e =expand("%:p:h") . "/" 
    else
        nnoremap ,e :e =expand("%:p:h") . "\\" 
    endif

    " Typical keypress errors:
    iabbr teh the

    " Abbreviations for some words in common use
    iabbr #c  # coding=utf-8
    iabbr #p  #!/usr/bin/env python
    iabbr #b  #!/bin/bash
" }

macOS 配置 golang 运行环境

存档下 macOS 下配置 golang 的过程。

第一步,通过 Homebrew 安装 golang 包

$ brew update
$ brew install go
$ go version
go version go1.12.1 darwin/amd64

第二步,配置 golang 工作环境

这里我们先要创建一个工作目录,后续所有工作都会在这个目录下展开:

$ mkdir -p /Users/kodango/Documents/Code/Go

配置 $GOPATH 等环境变量,这个操作是必须的,否则 go 命令运行的时候不知道去哪里寻找待执行的文件:

$ grep GO ~/.bash_profile
export GOPATH=$HOME/Documents/Code/Go    # 上面创建的工作目录
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN

接下来按照官网的建议,在工作目录下创建几个字目录:

$ mkdir -p $GOPATH $GOPATH/src $GOPATH/pkg $GOPATH/bin

第三步,测试是否工作正常

在 $GOPATH/src 目录下创建 hello.go 文件:

kodango -> ~/Documents/Code/Go/src/github.com/kodango/hello
$ ls -l
total 8
-rw-r--r--  1 kodango  staff  86  5 18 09:26 hello.go

写一个简单的 Hello world 示例代码:

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello, world\n")
}

使用 go run 命令运行代码:

kodango -> ~/Documents/Code/Go/src/github.com/kodango/hello
$ go run hello.go
Hello, world

或者先用 go install 将编译好的执行文件安装到 $GOBIN 目录。因为之前已经把 $GOBIN 添加到系统 $PATH,可以直接输入 hello 运行。

kodango -> ~/Documents/Code/Go/src/github.com/kodango/hello
$ go install hello.go
$ ls -l "$GOBIN"
total 4120
-rwxr-xr-x  1 kodango  staff  2108040  5 18 09:30 hello
$ hello
Hello, world

浅谈 Word 文档结构

近期工作中遇到一个增加导出为 Word 格式的需求,因此花了点时间仔细了解了下 docx 格式,发现原来一篇 Word 背后有如此复杂的结构。本文主要介绍 docx 文件的结构,但是 pptx、xlsx 的原理应该是类似的。

docx 格式的奥秘

对于 docx 文件大部分人都停留在这是一种 Office 的文件格式,可以用 Word 软件打开,并没有深入去了解下它内部的组成结构是怎么样的。实际上 docx 是一个压缩文件(Zip 格式),可以用 Zip 软件进行解压。

解压之后可以看到,它是有一系列 XML 文件组成:

这就是 docx 文件的奥秘。

OOXML 规范介绍

这些 XML 的用处、每个 XML 文件的定义格式、Zip 目录结构等,都是在 OOXML 规范定义的非常清楚,下面是 OOXML 官网对此的介绍:

Office Open XML, also known as OpenXML or OOXML, is an XML-based format for office documents, including word processing documents, spreadsheets, presentations, as well as charts, diagrams, shapes, and other graphical material. The specification was developed by Microsoft and adopted by ECMA International as ECMA-376 in 2006. A second version was released in December, 2008, and a third version of the standard released in June, 2011. The specification has been adopted by ISO and IEC as ISO/IEC 29500.

由此可见,word/ppt/excel 等几种格式的组织结构都是在同一个规范下定义的,他们的原理以及解析的方法都一样。OOXML 是微软公司在 2006 年公布的规范。类似的还有 Open Document Format (ODF),它是 OpenOffice.org 开源软件所使用的规范。

从官网介绍可以看出 OOXML 规范主要包括两个方面,第一个显然是每个 XML 文件的语法定义,类似 markdown 语言的语法描述一样,word 文件的格式、样式、内容布局等等都是和 XML 里面的 TAG 一一对应。第二个,就是对文件的组织结构的定义,前面看到 docx 实际上是一个 Zip 文件,解压缩出来的目录结构是有规则的,这个通过 Open Packaging Conventions (OPC) 定义,具体可以查看 Anatomy of a WordProcessingML File

查看全文

如何删除文件中的重复行

有时候删除文件里的重复行是一个很常见的需求,这个用 shell 命令有很多处理方法。

第一种方案是用 sort 命令的 -u 参数:

$ sort -u input.txt > output.txt

第二种方案是用 awk 命令,它的关键在于用一个字典来保存记录:

$ awk '!seen[$0]++' input.txt > output.txt

这和第一种方案的区别在于,即使文件中重复行不连续,依然可以删除。

第三种方案是用 sed 命令,但是其实不大推荐,它相比第一种方案复杂多,而且很容易写错:

$ sort -n input.txt | sed '$!N;/^\(.*\)\n\1$/!P;D' > output.txt

这种方案里面用到了 sed 的高级命令(N、P、D),相关介绍可以参考之前写的文章 Sed and awk 笔记之 sed 篇:高级命令(一)

这里有一个小技巧,如果遇到比较复杂的 sed 命令拿捏不准的化,可以用 sedsed 这个脚本来调试执行,看每一步执行的情况。

使用方法也很简单,比如调试上面这个 sed 命令,命令执行结果会展示每一步执行后模式空间和保持空间的内容,一目了然:

$ sort -n input.txt | python sedsed.py -d '$!N;/^\(.*\)\n\1$/!P;D'

谈谈近况

好久没有更新文章,博客都快长草了。距离上次更新文章已经两年多了,惭愧。

谈谈近况吧,现在已经从运维转回做开发了,算是和五年的运维生涯告一段落了。做开发的日子,相对轻松了不少,至少不需要半夜三更被叫起,或者一天到晚收报警短信,生活质量提升不少。其实我们这个行业发展是非常迅速的,之前一直在做 iaas 测的运维,那时容器也没现在这么火。但是,无论怎么样,运维依然是很重要的一个岗位,或者说是产品不可分割的一部分,最完美的就是产品设计之初就已经考虑到可运维性,现在看随着技术的发展以及基础设施的持续演化,之前梦寐以求的事情,未来也许会成为大家眼里的“基本”能力。

现在的团队负责内容平台的开发,基本算是换了一个全新的轨道,但是随着做的不断深入,发现这里面包含的内容、可以做的东西还是非常多的,我们的目标是要打造一个专业的内容管理平台。眼下离目标很远,但是那种看着自己的孩子慢慢长大的感觉,还是觉得非常值得,现在的团队规模并不大,但是我们还是非常缺人,如果大家有兴趣,可以私下交流。

新版 Firefox 试玩

记不清楚从什么时候开始使用 Firefox 的,用的第一个版本应该是 v3.6,当时觉得 Firefox 可定制性很强,可以按照自己的喜好折腾主题、样式、功能,也就逐渐迷上了火狐浏览器。也是大概在那期间(11年),自己开始业余时间学习 JavaScript,动手写一些 Greasemonkey 的脚本拓展网站功能。不过很遗憾,因为觉得 Firefox 的扩展入门比较难(后来开发 Chrome 扩展就发现非常容易),也就一直没有机会折腾一个扩展玩玩。

每一个曾经的 “Firefox” 粉应该都折腾过用户样式(Stylish)、油猴脚本(GreaseMonkey)、手势操作(FireGestures)、拖拽( Drag to go?)、下载以及标签定制这些扩展吧,这些也是当时 Firefox 受到大家追捧的很重要一个原因,几乎整个界面和功能都可以定制。当时甚至一度沉迷 Vimperator(以及Pentadactyl),打算不用鼠标,全键盘操作浏览器。

不过后来 Google Chrome 出现了,因为它非常简单、好用、启动快,而且自己也越来越不喜欢折腾,就叛变了,一直到换成 Mac 系统。现在主力是默认的浏览器 Safari,iMac、MacBook、iPhone、iPad 多个设备上基本可以保持一致的体验,开箱即用,Safari 我基本也没安装扩展,甚至广告过滤的扩展也没安装。

下午看到新闻说 Firefox 57 发布了,而且有人说速度比以前快多了,就下载了试玩了一下午,却是非常流畅。不过今非昔比,终究不是以前熟悉的 3.6 了。

New Firefox:

右键菜单体验很好,直接把前进、后退、刷新的功能放进来了,很方便。

Leetcode 3. Longest Substring Without Repeating Characters

继续做把第三题的解题思路放上来。第三题的目的是求字符串内最长的不重复子串,两个要求非常清晰。下面是题目的描述:

Given a string, find the length of the longest substring without repeating characters.

Examples:

Given "abcabcbb", the answer is "abc", which the length is 3.

Given "bbbbb", the answer is "b", with the length of 1.

Given "pwwkew", the answer is "wke", with the length of 3. Note that the answer must be a substring, "pwke" is a subsequence and not a substring.

思路:

假设子串的初始下标为 i,结束下标为 j。i 和 j 的初始值均为 0。j 开始一次往后遍历,如果这个字符串没有重复字符,那么 j 会一直走到最后,此时最长子串就是字符串本身。实际情况没有那么理想,我们主要分析这类场景。当处于 j 位置的字符和子串之前的字符重复(假设位置为 k),那么就应该停下,并且更新最长子串的长度(假设最长长度为 max),j 继续往后走,同时 i 的位置需要挪到重复字符之后,即 k+1 处。直到遍历到字符串的最后。

比如用第一个例子作为说明,初始状态 i = j = 0,最长子串长度 max = 0:

  1. j=0 处的字符是 a,无重复,当前子串长度为 1;
  2. j=1 处的字符是 b,无重复,当前子串长度为 2;
  3. j=2 处的字符是 b,无重复,当前子串长度为 3;
  4. j=3 处的字符是 a,发现重复,更新 max = 3,i = 0+1,当前子串长度为 3;
  5. j=4 处的字符是 b,发现重复,max 不变,i = 1+1,当前子串长度为 3;
  6. j=5 处的字符是 c,发现重复,max 不变,i = 2+1,当前子串长度为 3;
  7. j=6 处的字符是 b,发现重复,max 不变,i = 4+1,当前子串长度为 2;
  8. j=7 处的字符是 b,发现重复,max 不变,i = 6+1,当前子串长度为 1;

因此,"abcabcbb" 的最长子串长度是 3。

接下来我们写上代码:

func lengthOfLongestSubstring(s string) int {
	n := len(s)

	if n <= 1 {
		return n
	}

	tbl := make(map[byte]int, n)
	lo, longest, curr := 0, 0, 0

	for hi := 0; hi < n; hi++ {
		if k, ok := tbl[s[hi]]; ok && k >= lo {
			lo = k + 1
		}

		tbl[s[hi]] = hi
		curr = hi - lo + 1

		if curr > longest {
			longest = curr
		}
	}

	return longest
}

完整的代码参见这里,以及测试代码

LeetCode Problem 2 - Add Two Numbers

继续第二道题目,下面是问题描述:

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8

这个问题比较简单,基本上解题思路是比较清晰的。输入是两个链表,链表的元素都是单个数字(0-9),要求将两个列表的相应节点数字相加,并作为结果链表返回。

这个题咋看可以马上开始解答,但是在此之前还是有一些需要注意的地方。第一点是,题目并没有说明链表的长度,所以 A 和 B 两个链表可能不一定相同长度,那么如果一个链表更长,那么相加怎么处理呢?这里就考虑直接返回即可,相当于+0。第二点是,如果相加溢出怎么处理,其实题目的例子里面已经很清晰了,溢出会发生进位,依次向后处理。第三点是,如果最后一位发生进位呢,这点容易被遗忘,需要新增一个节点。

下面是具体的代码:

// Problem 2. Add Two Numbers
// URL: https://leetcode.com/problems/add-two-numbers/tabs/description

// ListNode defines a singly-linked list
type ListNode struct {
	Val  int
	Next *ListNode
}

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
	head := &ListNode{0, nil} // sentinel node
	carry := 0                // carray bit

	for tail := head; l1 != nil || l2 != nil || carry != 0; tail = tail.Next {
		sum := carry

		if l1 != nil {
			sum += l1.Val
			l1 = l1.Next
		}

		if l2 != nil {
			sum += l2.Val
			l2 = l2.Next
		}

		tail.Next, carry = &ListNode{sum % 10, nil}, sum/10
	}

	return head.Next // discard sentinel node
}

这里在链表加了一个哨兵节点,主要是为了处理方便。完整的代码参见这里,以及测试代码