碼天狗週刊 第 120 期 @vinta - Python, Docker, Kubernetes, Code Review

碼天狗週刊 第 120 期 @vinta - Python, Docker, Kubernetes, Code Review

本文同步發表於 CodeTengu Weekly - Issue 120

Remotely debug a Python app inside a Docker container in Visual Studio Code

之前因為開發環境都 containerized 了,但是偶爾又想要用一下 Visual Studio Code 的 Python debugger,所以就試了一下 Remote Debugging 功能,順便寫了一篇網誌記錄了跟 Flask 的 --reload 併用的奇技淫巧。忍不住抱怨一下,VS Code 的 Python debugger 雖然很炫炮,尤其是 "debug.inlineValues": true,但是啟動速度真的慢到靠北啊。

不過就像 @WanCW‏ 說的,與其一直花時間追求開發環境和正式環境的一致,整天搞那些 configs,還不如用那些時間多寫幾個 test cases 啊。

P.S. 這裡的 remote 主要指的是本機的某個 Docker container,當然也可以真的是遠端的某台機器。

kennethreitz/setup.py: A Human's Ultimate Guide to setup.py

也是出自 @kennethreitz 大神之手,不過這次不是什麼改變 Python 生態系的新專案,而是一個 setup.py 的範例,有自己的 Python package 的人可以參考看看。看了之後才知道,原來有 cmdclass 這麼一個用法啊。

Kubernetes Deconstructed: Understanding Kubernetes by Breaking It Down

在你讀完八百多頁的 Kubernetes 文件之後,這個影片可能會是個很好的複習。

Kubernetes NodePort vs LoadBalancer vs Ingress? When should I use what?

看了半天的 Kubernetes 官方文件,結果反而是在讀了這篇文章之後才終於搞清楚 ServiceIngress 的使用場景,嘖。

Getting started with Data Engineering

這篇文章概略地提到了作為一個 Data Engineer 可能會需要碰到的各項技術,一個人要完全掌握這些東西當然是不太可能啦,但是看了那個 Big Data Landscape 的圖還是覺得很血汗啊,大家感受一下。

不過讀完文章之後,最大的心得反而是所謂的 Data Engineer 做的事情跟 Web Backend Developer 其實很像啊,都是圍繞著 Data 本身打轉,在技術上也有相當大的重疊,最大的差別可能是在於人家用的那套 framework 叫做 Apache Spark,逼搞夏趴。

How to Do Code Reviews Like a Human (Part One)

雖然 code review 已經是老生常談了,但是這篇文章寫得真的是好。無論你是那個覺得 code review 是件苦差事的 reviewer 或是老是被要求改 code 改到往心裡去的 reviewee,又或者你們團隊根本還沒開始做 code review,本文都非常值得一讀。

延伸閱讀:

YouGlish - Use YouTube to improve your English pronunciation

因為公司的 System Architect 是加拿大人,所以平常跟他在開會或閒聊都是全英文進行,對土台灣人如我多少還是有點不習慣,最近的困擾是關於某些單字的唸法,一般的英文單字也就算了,畢竟網路上隨便一個字典都有真人發音。但是那些我們平常在用的技術詞彙(例如 framework 或 library 的名字)就不一定在字典裡找得到了。正好前陣子 @SammyLinTw 推薦了 YouGlish 這個網站,它可以直接幫你列出 YouTube 上關於某個單字的發音片段,實用!

大家感受一下:

延伸閱讀:

瑞克和莫蒂:我酗酒、我变态,但我是个好外公

(該文有大量劇透)

在 Twitter 上看到有人分享了 dannvix/NflxMultiSubs,幫 Netflix 加上「雙語字幕」功能的瀏覽器外掛。突然就想到前一陣子因為椎間盤突出在家裡休養的那段日子,除了物理治療和皮拉提斯之外,其他時間好像都是在看 Netflix 啊。說到這裡,忍不住要跟大家分享一下我印象最深的一部作品,Rick and Morty。不誇張,這部真的是我這輩子看過最屌的科幻動畫,有點像是把銀河便車指南裡的英式幽默用 Futurama 和辛普森家庭取代的感覺,第一次看就看傻了我。非常推薦!每一集的片尾都有一段彩蛋,千萬不要錯過了。

不過如果你真的不喜歡動畫,那也可以看 Dirk Gently's Holistic Detective Agency,原著小說的作者正是 Douglas Adams。

@vinta 分享。

sindresorhus/awesome-scifi: Sci-Fi worth consuming

科幻迷大放送!

延伸閱讀:

@vinta 分享。

Remotely debug a Python app inside a Docker container in Visual Studio Code

Remotely debug a Python app inside a Docker container in Visual Studio Code

Visual Studio Code with Python extension has "Remote Debugging" feature which means you could attach to a real remote host as well as a container on localhost. NOTE: While you trace Python code, the "Step Into" functionality is your good friend.

In this article, we are going to debug a Flask app inside a local Docker container through VS Code's fancy debugger, and simultaneously we are still able to leverage Flask's auto-reloading mechanism. It should apply to other Python apps.

ref:
https://code.visualstudio.com/docs/editor/debugging
https://code.visualstudio.com/docs/python/debugging#_remote-debugging

Install

On both host OS and the container, install ptvsd==3.0.0. Currently, later versions of PTVSD are experimentally supported.

$ pip3 install ptvsd==3.0.0

ref:
https://github.com/Microsoft/ptvsd
https://github.com/Microsoft/vscode-python/projects/6

Prepare

There are some materials and configurations. Assuming that you have a Dockerized Python Flask application like the following:

# Dockerfile
FROM python:3.6.6-alpine3.7 AS builder

WORKDIR /usr/src/app/

RUN apk add --no-cache --virtual .build-deps \
    build-base \
    openjpeg-dev \
    openssl-dev \
    zlib-dev

COPY requirements.txt .
RUN pip install --user -r requirements.txt

FROM python:3.6.6-alpine3.7 AS runner

ENV PATH=$PATH:/root/.local/bin
ENV FLASK_APP=app.py

WORKDIR /usr/src/app/

RUN apk add --no-cache --virtual .run-deps \
    openjpeg \
    openssl

EXPOSE 8000/tcp

COPY --from=builder /root/.local/ /root/.local/
COPY . .

CMD ["flask", "run"]
# docker-compose.yml
version: '3'
services:
    db:
        image: mongo:3.6
        ports:
            - "27017:27017"
        volumes:
            - ../data/mongodb:/data/db
    cache:
        image: redis:4.0
        ports:
            - "6379:6379"
    web:
        build: .
        command: .docker-assets/start-web.sh
        ports:
            - "3000:3000"
            - "8000:8000"
        volumes:
            - .:/usr/src/app
            - ../vendors:/root/.local
        depends_on:
            - db
            - cache

Usage

Method 1: Debug with --no-debugger, --reload and --without-threads

The convenient but a little fragile way: with auto-reloading enabled, you could change your source code on the fly. However, you might find that this method is much slower for the debugger to attach. It seems like --reload is not fully compatible with Remote Debugging.

We put ptvsd code to sitecustomize.py, as a result, ptvsd will run every time auto-reloading is triggered.

Steps:

  1. Set breakpoints
  2. Run your Flask app with --no-debugger, --reload and --without-threads
  3. Start the debugger with {"type": "python", "request": "attach", "preLaunchTask": "Enable remote debug"}
  4. Add ptvsd code to site-packages/sitecustomize.py by the pre-launch task automatically
  5. Click "Debug Anyway" button
  6. Access the part of code contains breakpoints
# site-packages/sitecustomize.py
try:
    import socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.close()
    import ptvsd
    ptvsd.enable_attach('my_secret', address=('0.0.0.0', 3000))
    print('ptvsd is started')
    # ptvsd.wait_for_attach()
    # print('debugger is attached')
except OSError as exc:
    print(exc)

ref:
https://docs.python.org/3/library/site.html

# .docker-assets/start-web.sh
rm -f /root/.local/lib/python3.6/site-packages/sitecustomize.py
pip3 install --user -r requirements.txt ptvsd==3.0.0
python -m flask run -h 0.0.0.0 -p 8000 --no-debugger --reload --without-threads
// .vscode/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Enable remote debug",
            "type": "shell",
            "isBackground": true,
            "command": " docker cp sitecustomize.py project_web_1:/root/.local/lib/python3.6/site-packages/sitecustomize.py"
        }
    ]
}

ref:
https://code.visualstudio.com/docs/editor/tasks

// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Attach",
            "type": "python",
            "request": "attach",
            "localRoot": "${workspaceFolder}",
            "remoteRoot": "/usr/src/app",
            "port": 3000,
            "secret": "my_secret",
            "host": "localhost",
            "preLaunchTask": "Enable remote debug"
        }
    ]
}

ref:
https://code.visualstudio.com/docs/editor/debugging#_launch-configurations

Method 2: Debug with --no-debugger and --no-reload

The inconvenient but slightly reliable way: if you change any Python code, you need to restart the Flask app and re-attach debugger in Visual Studio Code.

Steps:

  1. Set breakpoints
  2. Add ptvsd code to your FLASK_APP file
  3. Run your Flask app with --no-debugger and --no-reload
  4. Start the debugger with {"type": "python", "request": "attach"}
  5. Access the part of code contains breakpoints
# in app.py
import ptvsd
ptvsd.enable_attach('my_secret', address=('0.0.0.0', 3000))
print('ptvsd is started')
# ptvsd.wait_for_attach()
# print('debugger is attached')

ref:
http://ramkulkarni.com/blog/debugging-django-project-in-docker/

# .docker-assets/start-web.sh
pip3 install --user -r requirements.txt ptvsd==3.0.0
python -m flask run -h 0.0.0.0 -p 8000 --no-debugger --no-reload
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Attach",
            "type": "python",
            "request": "attach",
            "localRoot": "${workspaceFolder}",
            "remoteRoot": "/usr/src/app",
            "port": 3000,
            "secret": "my_secret",
            "host": "localhost"
        }
    ]
}

Method 3: Just don't use Remote Debugging, Run Debugger locally

You just run your Flask app on localhost (macOS) instead of putting it in a container. However, you could still host your database, cache server and message queue inside containers. Your Python app communicates with those services through ports which exposed to 127.0.0.1. Therefore, you could just use VS Code's debugger without strange tricks.

In practice, it is okay that your local development environment is different from the production environment.

# docker-compose.yml
version: '3'
services:
    db:
        image: mongo:3.6
        ports:
            - "27017:27017"
        volumes:
            - mongo-volume:/data/db
    cache:
        image: redis:4.0
        ports:
            - "6379:6379"
volumes:
    mongo-volume:
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Flask",
            "type": "python",
            "request": "launch",
            "module": "flask",
            "pythonPath": "${config:python.pythonPath}",
            "cwd": "${workspaceFolder}",
            "envFile": "${workspaceFolder}/.env",
            "args": [
                "run",
                "-h",
                "0.0.0.0",
                "-p",
                "8000",
                "--no-debugger",
                "--no-reload"
            ],
            "debugOptions": [
                "RedirectOutput",
                "DebugStdLib"
            ],
            "stopOnEntry": false
        }
    ]
}

Sadly, you cannot use --reload while launching your app in the debugger. Nevertheless, most of the time you don't really need the debugger - a fast auto-reloading workflow is good enough. All you need is a Makefile for running Flask app and Celery worker on macOS: make run_web and make run_worker.

# Makefile
install:
    pipenv install
    pipenv run pip install git+https://github.com/gorakhargosh/watchdog.git

shell:
    pipenv run python -m flask shell

run_web:
    pipenv run python -m flask run -h 0.0.0.0 -p 8000 --debugger --reload

run_worker:
    pipenv run watchmedo auto-restart -d . -p '*.py' -R -- celery -A app:celery worker -l info -E -P gevent -Ofair

Bonus

You should try enabling debug.inlineValues which shows variable values inline in editor while debugging. It's awesome!

// settings.json
{
    "debug.inlineValues": true
}

ref:
https://code.visualstudio.com/updates/v1_9#_inline-variable-values-in-source-code

Issues

Starting the Python debugger is fucking slow
https://github.com/Microsoft/vscode-python/issues/106

Debugging library functions won't work currently
https://github.com/Microsoft/vscode-python/issues/111

Pylint for remote projects
https://gist.github.com/IBestuzhev/d022446f71267591be76fb48152175b7

Must Have Sublime Text 3 Packages

Must Have Sublime Text 3 Packages

AceJump

link: https://github.com/ice9js/ace-jump-sublime

AceJump allows you to move the cursor to any character to any place currently on screen

All Autocomplete

link: https://github.com/alienhard/SublimeAllAutocomplete

Extend Sublime autocompletion to find matches in all open files of the current window

ANSIescape (X)

link: https://packagecontrol.io/packages/ANSIescape

Ansible

link: https://github.com/clifford-github/sublime-ansible

Syntax highlighting for Ansible

ApplySyntax

link: https://github.com/facelessuser/ApplySyntax

Applying syntax by custom rules

Preferences > Package Settings > ApplySyntax > Settings - User

{
    "reraise_exceptions": false,
    "new_file_syntax": false,
    "syntaxes": [
        {
            "name": "Djaneiro/Syntaxes/HTML (Django)",
            "match": "all",
            "rules": [
                {"file_name": ".*\\.(html|htm)$"},
                {"first_line": "{%"}
            ]
        },
        {
            "name": "Djaneiro/Syntaxes/Python Django",
            "match": "all",
            "rules": [
                {"file_name": ".*\\.py$"},
                {"contains": "from django"}
            ]
        },
        {
            "name": "Djaneiro/Syntaxes/Python Django",
            "rules": [
                {"file_name": ".*(\\\\|/)local_settings.py$"},
                {"file_name": ".*(\\\\|/)settings.py$"}
            ]
        },
        {
            "name": "Python/Python",
            "rules": [
                {"file_name": ".*\\.py$"}
            ]
        }
    ]
}

AutoPEP8 (X)

link: https://github.com/wistful/SublimeAutoPEP8

Automatically formats Python code to conform to the PEP 8.

Babel

link: https://packagecontrol.io/packages/Babel

Syntax definitions for ES6 JavaScript with React JSX extensions.

BigComment (X)

link: https://github.com/manse/BigComment

Generate ascii-art comment to display text on Minimap.

$ cd "/Users/vinta/Library/Application Support/Sublime Text 3/Packages"
$ git clone [email protected]:manse/BigComment.git

Command + Option + C

BracketHighlighter

link: https://github.com/facelessuser/BracketHighlighter/

Brackets and Tags highlighter.

BufferScroll (X)

link: https://github.com/SublimeText/BufferScroll

Remember, restore and sync scroll, bookmarks, marks, folds and cursors

Case Conversion (X)

link: https://github.com/jdc0589/CaseConversion

Toggle variable naming styles.

Control + Option + C > Control + Option + S >> under_score
Control + Option + C > Control + Option + P >> PascalCase
Control + Option + C > Control + Option + C >> camelCase

Chinese​Open​Convert

link: https://packagecontrol.io/packages/ChineseOpenConvert

String translation between Traditional Chinese and Simplified Chinese.

ColorPicker (X)

link: https://github.com/weslly/ColorPicker

Command + Shift + C

Color Highlighter (X)

link: https://github.com/Monnoroch/ColorHighlighter

把游標移進去之後,會自動把 #FFFFFF 之類的字串變成對應的顏色

Color Scheme - Behave

link: https://github.com/fnky/behave-theme

CSS Completions (X)

link: https://github.com/daneden/sublime-css-completions

DashDoc

link: https://github.com/Kapeli/DashDoc

Control + D
用游標所在字詞搜尋 Dash

in Dash: Preferences > General > Global search shorcut -- Shift + D
in System Preferences: Keyboard > Shortcuts > Services > Searching > Look Up in Dash -- Control + D

Preferences > Key Bindings - User

[
    {"command": "dash_doc", "keys": ["ctrl+d"]},
]

Djaneiro

link: https://github.com/squ1b3r/Djaneiro

Control + Space

Dotfiles Syntax Highlighting

link: https://github.com/mattbanks/dotfiles-syntax-highlighting-st2

Bring ShellScript (Bash) syntax highlighting to dotfiles

EncodingHelper

link: https://github.com/SublimeText/EncodingHelper

Guess encoding of files, show in status bar, convert to UTF-8 from a variete of encodings

Expand​Region

link: https://github.com/aronwoost/sublime-expand-region

A better "Expand Selection to Scope".

Find++ (X)

link: https://github.com/twolfson/FindPlusPlus

Find in current folder, Find in open files...

FuzzyFilePath (X)

link: https://github.com/sagold/FuzzyFilePath

Preferences > Key Bindings - User

[
    {"command": "insert_path", "keys": ["ctrl+t"], "args": {"type": "absolute"}},
    ...
]

Generic Config

link: https://github.com/skozlovf/Sublime-GenericConfig

Generic highlighting of the configuration files

Git

link: https://github.com/kemayo/sublime-text-git

Preferences > Package Settings > Git > Settings - User

{
    "diff_panel": true,
    "statusbar_branch": true,
    "statusbar_status": false
}

GitHubinator

link: https://github.com/ehamiter/GitHubinator

Open a corresponding page of current file on GitHub.

GitSavvy (X)

link: https://github.com/divmain/GitSavvy/

Full git and GitHub integration

Godef

link: https://github.com/buaazp/Godef

Better than GoSublime's Goto Definition.

$ go get -u github.com/rogpeppe/godef

Google Search

link: https://github.com/nwjlyons/google-search

Search Google for the currently selected text.

GoSublime

link: https://github.com/DisposaBoy/GoSublime

$ go get -u github.com/nsf/gocode

HTML5

link: https://github.com/mrmartineau/HTML5

HTML-CSS-JS Prettify

link: https://github.com/victorporof/Sublime-HTMLPrettify

Command + Shift + H(or 右鍵選單)

Java​Script Completions

link: https://sublime.wbond.net/packages/JavaScript%20Completions

JavaScript Next

link: https://github.com/Benvie/JavaScriptNext.tmLanguage

Better JavaScript language definition.

Jedi

link: https://github.com/srusskih/SublimeJEDI

Awesome Python autocompletion with SublimeText 3.

Control + Option + G (or CONTROL + 左鍵)
Go to definition
建議改成 Control + G

Control + Option + F (or OPTION + 左鍵)
Find usages
建議改成 Control + F

Preferences > Key Bindings - User

[
    {"command": "sublime_jedi_find_usages", "keys": ["ctrl+f"], "context": [{"key": "selector", "operator": "equal", "operand": "source.python - string - comment"}]},
    {"command": "sublime_jedi_goto", "keys": ["ctrl+g"], "context": [{"key": "selector", "operator": "equal", "operand": "source.python - string - comment"}]}
]
[
    {"command": "sublime_jedi_goto", "modifiers": ["ctrl"], "button": "button1", "press_command": "drag_select"},
    {"command": "sublime_jedi_find_usages", "modifiers": ["alt"], "button": "button1", "press_command": "drag_select"}
]

in YOUR_PROJECT.sublime-project

{
    "folders":
    [
        {
            "path": "/Users/vinta/Projects/codebaku-web"
        },
        {
            "path": "/Users/vinta/Projects/codebaku-hubot"
        }
    ],
    "settings":
    {
        "python_interpreter": "/Users/vinta/.pyenv/versions/3.4.3/bin/python3.4",
        "python_package_paths":
        [
            "/Users/vinta/Dropbox/Developments/virtualenv_codebaku/dist-packages"
        ]
    }
}

jQuery

link: https://github.com/SublimeText/jQuery

Snippets for jQuery.

KeybardSpellCheck

link: https://github.com/jlknuth/KeyboardSpellCheck

Control + Alt + K

Keymaps

link: https://github.com/MiroHibler/sublime-keymaps

Control + Option + ?

LineJumper

link: https://github.com/hypebeast/LineJumper

Move your cursor and select 10 lines at a time.

Fn + 方向鍵
游標移動 1 頁

Option + 方向鍵
游標移動 n 行

Preferences > Key Bindings - User

[
    {"command": "line_jumper", "keys": ["alt+up"], "args": {"number_of_lines": 5, "cmd": "up"}},
    {"command": "line_jumper", "keys": ["alt+down"], "args": {"number_of_lines": 5, "cmd": "down"}},
    ...
]

Markdown Extended

link: https://github.com/jonschlinkert/sublime-markdown-extended

Supporting GitHub Flavored Markdown

Modific

link: https://github.com/gornostal/Modific

Highlight lines changed since the last commit

Pretty JSON

link: https://github.com/dzhibas/SublimePrettyJson

Formating JSON.

$ brew install jq

Preferences > Package Settings > Pretty JSON > Settings - User

{
    "indent": 4
}

PyCover (X)

link: https://packagecontrol.io/packages/PyCover

Showing coverage infomation in Sublime Text.

Python Improved

link: https://github.com/MattDMo/PythonImproved

A better Python .tmLanguage definition.

requirementstxt

link: https://github.com/wuub/requirementstxt

Syntax highlighting for requirements.txt.

Select Quoted

link: https://github.com/int3h/SublimeSelectQuoted

Command + "
選擇 "" 或 '' 內的文字

Command + (
選擇 () 的文字

Preferences > Key Bindings - User

[
    {"command": "expand_selection", "args": {"to": "brackets"}, "keys": ["super+9"]},
    ...
]

SideBarEnhancements

link: https://github.com/titoBouzout/SideBarEnhancements

StringUtilities

link: https://packagecontrol.io/packages/StringUtilities

Sublimall

link: https://github.com/vinta/sublimall-server

Synchronize your Sublime Text preferences and packages configurations

原作者已經停止開發了
不過我 fork 了一個版本
加上 deploy 到 Heroku 的功能

$ brew install p7zip

Preferences > Package Settings > Sublimall > Settings - User

{
    "email": "[email protected]",
    "api_key": "YOUR_API_KEY",
    "7za_path": "/usr/local/bin/7za"
}

sublime-github

link: https://github.com/bgreenlee/sublime-github

SublimeLinter

link:
https://github.com/SublimeLinter/SublimeLinter3
https://github.com/SublimeLinter/SublimeLinter-annotations
https://github.com/SublimeLinter/SublimeLinter-flake8
https://github.com/SublimeLinter/SublimeLinter-json
https://github.com/sirreal/SublimeLinter-contrib-golint
https://github.com/sirreal/SublimeLinter-contrib-govet

各語言的 coding style 和 syntax 檢查
SublimeLinter 3 並不包含 linter,要自己用 Package Control 安裝

Preferences > Package Settings > SublimeLinter > Settings - User

{
    "user": {
        "debug": false,
        "delay": 0.65,
        "error_color": "D02000",
        "gutter_theme": "Packages/SublimeLinter/gutter-themes/Default/Default.gutter-theme",
        "gutter_theme_excludes": [],
        "lint_mode": "background",
        "linters": {
            "annotations": {
                "@disable": false,
                "args": [],
                "errors": [
                    "DIRTY HACK",
                    "FIXME",
                    "TEST"
                ],
                "excludes": [],
                "warnings": [
                    "CAUTION",
                    "TODO",
                    "README"
                ]
            },
            "flake8": {
                "@disable": false,
                "args": [],
                "builtins": "",
                "excludes": [],
                "executable": "",
                "ignore": "E501",
                "jobs": "1",
                "max-complexity": -1,
                "max-line-length": 999,
                "select": "",
                "show-code": true
            },
            "golint": {
                "@disable": false,
                "args": [],
                "excludes": []
            },
            "govet": {
                "@disable": false,
                "args": [],
                "excludes": []
            },
            "json": {
                "@disable": false,
                "args": [],
                "excludes": [],
                "strict": true
            }
        },
        "mark_style": "fill",
        "no_column_highlights_line": true,
        "passive_warnings": false,
        "paths": {
            "linux": [],
            "osx": [
                "/Users/vinta/.pyenv/versions/2.7.10/bin",
                "/Users/vinta/.pyenv/versions/3.5.0/bin",
                "/Users/vinta/.gvm/gos/go1.5.3/bin",
                "/Users/vinta/Projects/oh-my-go/bin"
            ],
            "windows": []
        },
        "python_paths": {
            "linux": [],
            "osx": [],
            "windows": []
        },
        "rc_search_limit": 0,
        "shell_timeout": 10,
        "show_errors_on_save": false,
        "show_marks_in_minimap": false,
        "syntax_map": {
            "html (django)": "html",
            "html 5": "html",
            "python django": "python",
            "pythonimproved": "python"
        },
        "warning_color": "DDB700",
        "wrap_find": true
    }
}

tern_for_sublime

link: https://github.com/marijnh/tern_for_sublime

Handling autocompletion for JavaScript by tern.js.

$ cd "/Users/vinta/Library/Application Support/Sublime Text 3/Packages/"
$ git clone git://github.com/marijnh/tern_for_sublime.git

$ cd tern_for_sublime
$ npm install

Preferences > Settings - User

{
    "auto_complete": true,
    "auto_complete_triggers":
    [
        {
            "selector": "source.js",
            "characters": "."
        }
    ],
    ...
    "tern_argument_hints": true,
    "tern_arguments": [
         "--no-port-file"
    ],
    "tern_command":
    [
         "/Users/vinta/.nvm/v0.10.32/bin/node",
         "/Users/vinta/.nvm/v0.10.32/bin/tern"
    ],
    ...
}

in PROJECT_ROOT/.tern-project

{
    "libs": [
        "angular",
        "browser",
        "jquery"
    ]
}

OPTION + .
Go to definition

ref:
http://ternjs.net/doc/manual.html

Theme - Afterglow

link: https://github.com/YabataDesign/afterglow-theme

Theme - itg.flat

link: https://github.com/itsthatguy/theme-itg-flat

ToggleQuotes

link: https://github.com/spadgos/sublime-ToggleQuotes

建議改成 Control + '

--

Preferences > Key Bindings - User

[
    {"command": "ace_jump_char", "keys": ["super+shift+'"]},
    {"command": "better_bookmarks_clear_all_marks", "keys": ["shift+f12"]},
    {"command": "better_bookmarks_clear_marks", "keys": ["f12"]},
    {"command": "better_bookmarks_mark_line", "keys": ["f11"]},
    {"command": "dash_doc", "keys": ["ctrl+d"]},
    {"command": "expand_selection", "keys": ["alt+."], "args": {"to": "tag"}},
    {"command": "expand_selection", "keys": ["alt+0"], "args": {"to": "brackets"}},
    {"command": "find_under_expand", "keys": ["super+d"]},
    {"command": "fpp_find_in_project", "keys": ["super+shift+g"]},
    {"command": "godef", "keys": ["ctrl+g"], "context": [{"key": "selector", "operator": "equal", "operand": "source.go"}]},
    {"command": "google_search", "keys": ["ctrl+q"]},
    {"command": "gs_browse_declarations", "keys": ["ctrl+f"], "args": {"dir": "."}, "context": [{"key": "selector", "operator": "equal", "operand": "source.go"}]},
    {"command": "gs_doc", "keys": ["ctrl+h"], "args": {"mode": "hint"}, "context": [{"key": "selector", "operator": "equal", "operand": "source.go"}]},
    {"command": "gs_inline_diff", "keys": ["ctrl+c"]},
    {"command": "gs_show_status", "keys": ["ctrl+s"]},
    {"command": "insert_snippet", "keys": ["ctrl+shift+v"], "args": {"contents": "from IPython import embed; embed()"}},
    {"command": "jump_back", "keys": ["alt+s"]},
    {"command": "jump_forward", "keys": ["alt+w"]},
    {"command": "line_jumper", "keys": ["alt+down"], "args": {"number_of_lines": 5, "cmd": "down"}},
    {"command": "line_jumper", "keys": ["alt+shift+down"], "args": {"number_of_lines": 5, "cmd": "down_select"}},
    {"command": "line_jumper", "keys": ["alt+shift+up"], "args": {"number_of_lines": 5, "cmd": "up_select"}},
    {"command": "line_jumper", "keys": ["alt+up"], "args": {"number_of_lines": 5, "cmd": "up"}},
    {"command": "next_bookmark" , "keys": ["f10"]},
    {"command": "open_dir", "keys": ["alt+f"], "args": {"dir": "$file_path", "file": "$file_name"}},
    {"command": "open_file", "keys": ["super+alt+k"], "args":{"file": "${packages}/User/Default (OSX).sublime-keymap", "platform": "OSX"}},
    {"command": "prev_bookmark", "keys": ["f9"]},
    {"command": "reveal_in_side_bar", "keys": ["alt+r"]},
    {"command": "select_quoted", "keys": ["alt+'"]},
    {"command": "show_overlay", "keys": ["super+e"], "args": {"overlay": "goto", "text": ":"}},
    {"command": "show_overlay", "keys": ["super+t"], "args": {"overlay": "goto", "text": "#"}},
    {"command": "side_bar_project_open_file", "keys": ["super+alt+p"]},
    {"command": "spell_check", "keys": ["ctrl+t"]},
    {"command": "sublime_jedi_find_usages", "keys": ["ctrl+f"], "context": [{"key": "selector", "operator": "equal", "operand": "source.python - string - comment"}]},
    {"command": "sublime_jedi_goto", "keys": ["ctrl+g"], "context": [{"key": "selector", "operator": "equal", "operand": "source.python - string - comment"}]},
    {"command": "toggle_side_bar", "keys": ["super+\\"]}
]