使用 create-react-library 快速開發獨立 component library
想要開發獨立的 React component library 卻不知從何下手?在工作專案中開發了一堆 component 卻怎樣拆也拆不開?聽說有 create-react-library
這個神奇工具可以快速地解決這些麻煩,立馬來試試。
安裝
yarn global add create-react-library
使用 create-react-library
安裝好之後就可以馬上用它的 CLI 試試,先設定個目標—一個沒用的按鈕(UselessButton)好了。
$ create-react-library ? Package Name useless-button ? Package Description Test for create-react-library ? Author's GitHub Handle ccmikechen ? GitHub Repo Path ccmikechen/useless-button ? License MIT ? Package Manager yarn ? Template default ✔ Copying default template to /home/mike/workspace/useless-button/useless-button ✔ Running yarn install and yarn link ✔ Initializing git repo Your module has been created at /home/mike/workspace/useless-button/useless-button. To get started, in one tab, run: $ cd useless-button && yarn start And in another tab, run the create-react-app dev server: $ cd useless-button/example && yarn start
這樣就通通幫你建完了。繼續照著指示做。
開發用 Server
create-react-library
很貼心的提供開發用 Server 讓你一邊開發一邊看結果。
cd example && yarn start
之後在瀏覽器開啟 http://localhost/
就可以看到 component 的樣子了。
開發 Component
現在一切準備妥當,開始來開發我們的沒用的按鈕。
// src/index.js import React, { Component } from 'react' import PropTypes from 'prop-types' import styles from './styles.css' export default class UselessButton extends Component { static propTypes = { text: PropTypes.string } render() { const { text } = this.props return ( <button className={styles.button} onClick={() => alert("You are useless!")}> { text } </button> ) } }
/* src/styles.css */ .button { display: inline-block; margin: 2em auto; border: 2px solid #000; font-size: 2em; }
// example/src/App.js import React, { Component } from 'react' import UselessButton from 'useless-button' export default class App extends Component { render () { return ( <div> <UselessButton text='Useless' /> </div> ) } }
接下來看到畫面上出現了我們的沒用的按鈕,按下去會跳出 alert ,功能正常,接下來就可以發布囉!
發布
要發布自己的 library 到 npm 之前必須先擁有 npm 的帳號,沒有的先去 npm 官網申請一個吧! 接下來簡單透過指令發布:
yarn publish
這樣就完成了,可以到 npm 的頁面找找看。
https://www.npmjs.com/package/useless-button
測試
既然已經發布到 npm 上了,那就來試試看吧!
我們可以使用 create-react-app
建立一個新專案試試。
yarn global add create-react-app create-react-app useless-button-test cd useless-button-test yarn start
這樣測試用的 server 也開好了,將我們的 library 加進專案中。
yarn add useless-button
// useless-button-test/src/App.js import React, { Component } from 'react'; import UselessButton from 'useless-button'; import logo from './logo.svg'; import './App.css'; class App extends Component { render() { return ( <div className="App"> <UselessButton text="Useless" /> </div> ); } } export default App;
接下來畫面上就能看到我們剛剛開發的 UselessButton
囉!
更安全的 JavaScript functional programming - Immutable.js
之前的 JavaScript 中的 Functional programmimg 文章中提到有關在 JS 中的 Functional Programming (FP),也提到 ES6 中有 let
與 const
可以用來宣告變數。如果要完全依照 FP 的原則來開發 JS 專案的話,基本上要把 let
給捨棄掉,但只用 const
的話真的就安全了嗎?
const 的不足
首先來看一下對使用 const
宣告的常數重新設值的話會發生什麼事。
> const a = 1 undefined > a = 2 TypeError: Assignment to constant variable. > a 1
很明顯的會出現 TypeError
,a
的值也維持在 1
不變。那我們再來看一個例子:
> const a = [1, 2, 3] undefined > a[0] = 4 4 > a [ 4, 2, 3 ]
雖然 a
已經用 const
宣告為常數了,但對於 array 之中的值它就不起作用了,明顯違反 FP 原則。
使用 Immutable.js
幸好 Facebook 為此開發了這個強大的套件 Immutable.js ,透過這個套件就不須擔心上述的情況了。
安裝
首先透過 yarn
來將 Immutable.js 加入專案中:
$ yarn add immutable
Immutable.js 提供了 Map
、 List
、 Set
、 Stack
...等各種 collection ,且包含了每種 collection 各自的方法,非常容易使用。
List
首先嘗試一下 List
:
const Immutable = require('immutable'); const a = new Immutable.List([1, 2, 3]); a.set(0, 4); console.log(a.toJS()); // [ 1, 2, 3 ]
首先建立一個新的 List
物件,可以直接用 new
並傳入一個 array 來建立,也可以透過 Immutable.fromJS()
方法來建立。List
設定 item 的值須透過 set()
方法,反過來要取值的話則是用 get()
方法,最後可以用 toJS()
方法來將 List
物件轉換回一般的 array 。
可以注意到第三行使用了 set()
對 a
的 item 設值,但列印出來卻沒變。
const Immutable = require('immutable'); const a = new Immutable.List([1, 2, 3]); const b = a.set(0, 4); console.log(a.toJS()); // [ 1, 2, 3 ] console.log(b.toJS()); // [ 4, 2, 3 ]
這下就明白了,set()
方法並不會對原 List
物件本身產生副作用,而是回傳一個新的 List
物件,因此保證了 List
本身的不變性,這正是 FP 該有的性質。
除了 set()
與 get()
之外還有許多 List
的方法可以使用,可參考官方文件。
Map
JS 的 map 其實就是個 object ,但同樣也會有類似情況:
> const a = { b: 1 } undefined > a['b'] = 2 2 > a { b: 2 }
因此我們可以使用 Immutable.js 提供的 Map
來防止這類事情發生。
const Immutable = require('immutable'); const a = new Immutable.Map({ b: 1 }); const b = a.set('b', 2); console.log(a.toJS()); // { b: 1 } console.log(b.toJS()); // { b: 2 }
如果 Map
比較多層,也可以透過 setIn()
與 getIn()
方法來存取:
const Immutable = require('immutable'); const a = new Immutable.Map({ b: { c: 1 }}); const b = a.setIn(['b', 'c'], 2); console.log(a.toJS()); // { b: { c: 1 } } console.log(b.toJS()); // { b: { c: 2 } }
JavaScript 中的 Functional programmimg
Functional Programming (FP) 這個概念已經行之有年了,熱門的純 FP language 有 Haskell 為代表,許多語言也宣稱自己有 FP 也就是非純 FP language,包括 Erlang 、 Clojure 、F# 、 Common Lisp 等,可以一邊享受 OOP 抽象化的強大,也可以享受 FP 的簡潔直觀,對於開發效率上是很有幫助的。
Functional Programming 的特性
關於 FP 最重要的特性就是 pure function , 一個 function 要能夠被稱為 pure function 所必須具備的就是 function 的不變性,也就是一個輸入對應一個輸出。
function plus(a, b) { return a + b; }
這個 plus()
就是一個 pure function ,只要每次給的 a
與 b
的值一樣,那輸出結果就一樣。
a = 0 function count() { return a++; }
這個 count()
就不是一個 pure function ,每呼叫一次得到的值都不一樣,而且具有副作用,所以不能被稱之為 pure function 。
function rand() { return parseInt(Math.random() * 10); }
這個 rand()
也不是一個 pure function ,雖然沒有副作用,但每次呼叫的結果也都不一樣。
變數不能變
許多比較純的 FP 語言都不允許你對一個定義過的變數重新賦值,例如在 Erlang 中,當你對某個變數賦值就會引發例外。
1> A = 1. 1 2> A = 2. ** exception error: no match of right hand side value 2
在 ES6 當中 JS 的變數定義又被分為了 let
與 const
兩種,前者是能夠被重新賦值的,後者就跟常數一樣。隨著每個專案的風格,有些專案中因為使用 FP 的開發模式,所以完全不會出現 let
。那在這種情況下要怎樣做迴圈呢?一般的 For-Loop 迴圈在每次的 loop 都會改變某個值,例如:
for (var i = 0; i <= 10; i++) { console.log(i) }
在這個迴圈當中 i
不斷的被遞增,違反了變數不變的原則,所以像這樣的迴圈是不會出現在 Erlang 當中的。那究竟怎樣才能夠在 FP 中實現迴圈呢?
遞迴
不能用 For-Loop 的話,那就只能用遞迴了,我們將上面的例子用遞迴重寫看看。
const loop = (from, to) => { console.log(from); if (from < to) { loop(from + 1, to); } } loop(0, 10)
這個 loop()
就是一個 pure function ,而且輸出的結果跟上面 For-Loop 版本一樣,也許你會覺得 from
不是一直被變動嗎?但其實不然, from
其實沒有被重新賦值過,每次呼叫 loop()
中的 from
其實都指向不一樣的位置,只是都只用同一個變數名稱而已。
forEach, map, reduce
雖然說遞迴就能夠取代一般的 For-Loop 在 FP 的角色,但每次需要用到迴圈處理 list 或 array 時都要用遞迴不是很麻煩嗎?因此大部分的 FP 語言都會內建 forEach
、 map
、 reduce
這三大 list 處理函數讓你用,在 JS 中 forEach
、 map
、 reduce
都是 array 的方法。
forEach
可以接受一個 function 作為參數,然後迭代 array 的所有 item ,並將 item 作為參數呼叫 function 。
> [1, 2, 3].forEach(i => console.log(i)) 1 2 3
map
則跟 forEach
類似,但會輸出一個一樣長度的 array , array 內的每個 item 對應到所給 function 呼叫後的結果。
> [1, 2, 3].map(i => i * 2) [ 2, 4, 6 ]
reduce
就比較複雜,它接受一個初始值與一個 function ,一樣會迭代 array ,但 function 每次接收到的引數除了 item 之外還多一個每次 function 執行完的結果,而 reduce
最後的結果就是最後一次迭代 function 的回傳值。
假如我要實做一個將 list 內的所有值加總的函數,一般的寫法是:
const sum = list => { let result = 0; for (let i = 0; i < list.length; i++) { result += list[i]; } return result; }
而 FP 的寫法是:
const sum = list => (
list.reduce((acc, i) => acc + i, 0)
)
這樣是不是簡潔多了?仔細觀察一樣 acc
與 i
的變化:
[1, 2, 3].reduce((acc, i) => { console.log(`acc: ${acc}, i: ${i}`); return acc + i; }, 0) // acc: 0, i: 1 // acc: 1, i: 2 // acc: 3, i: 3
React Native 開發 APP 不用甩手機,在 Emacs 輕鬆 Reload
雖然 React Native 提供非常方便的 reload 功能,讓你不需要重新安裝 APP 就能即時更新,但仍然有許多人使用 React Native 開發 APP 時遇到最煩的一個步驟就是「每次 reload 都要甩手機」,甩甩手機跑出選單後按 reload 來載入 bundle 更新畫面。好好一隻手機乖乖放在桌上,但三不五時就要拿起來狂甩實在煩人。甚至有時候怎麼甩選單都出不來,一氣之下可能就把手機往地上摔了。
Reload 一定要甩手機嗎?
當然還有其他方法可以讓你快速地 reload APP ,在 Android 中只要想辦法能透過鍵盤輸入 RR
文字就能夠直接觸發 reload ,但正常情況下我們的手機都沒有可以直接按的鍵盤,那要怎麼輸入 RR 呢?
透過 adb 輸入文字
使用 adb
就可以發送鍵盤事件讓手機接收到文字輸入。
$ adb shell input text "RR"
試著輸入這串指令看看,可以觀察到不需要打開選單按來按去 APP 就這樣 reload 了!好吧,這段指令似乎有點長,也許你會說:「設個 alias 吧!」
$ alias rr="adb shell input text 'RR'" $ rr
這麼一來每次要 reload 的時候只要下 rr
指令即可。
在 Emacs 設定快捷鍵
雖然說可以透過指令來進行 reload 看上去已經足夠了,但我們還可以進一步地在最強編輯器 Emacs 上給個快捷鍵進行 reload 。
(defun rn-reload () (interactive) (shell-command "adb shell input text 'RR'")) (global-set-key (kbd "C-c r") 'rn-reload)
太棒了!現在當我們開發到一半想看看結果時,不需要切換畫面,只要按下 C-c r
就能快速 reload APP 了。
Emacs 入坑引導 - 打造自己的 Ruby IDE - Part 2
Emacs 入坑引導 - 打造自己的 Ruby IDE - Part 2
前言
我在 Part 1 簡單介紹了 Emacs 入門的基礎知識,包括基本按鍵操作、套件安裝及視窗管理等,熟練 Emacs 需要長期使用經驗的累積,大腦才會慢慢適應,Coding 的速度才會跟上思考。在 Part 2 我將繼續介紹如何透過設定 Emacs 來打造自己的 Ruby IDE 。
何謂 IDE ?
IDE 中文名為「整合開發環境」(Integrated Development Environment),一個單純的 Editor 如 Nano 等簡單的文字編輯器是無法被稱為 IDE 的,IDE 必須包含各種開發專案需要的工具,從 Code editing 、 Debugging 、 Compiling 、 Executing 等等,好的 IDE 還可以包含 Syntax highlighting 、 Code search 、 Refactoring 、 Auto formatting 、 Version control 、 Project management 、 Terminal 等。比較熱門的 IDE 有 Microsoft Visual Studio 、 Eclipse 、 NetBeans 等,每個 IDE 都有針對不同程式語言與框架的專案開發支援,如 Microsoft Visual Studio 支援 .Net Framework 的開發,Eclipse 則最主要用來開發 Java 。
Ruby IDE
若要開發 Ruby 或 Ruby on Rails 的話有現成的 IDE 嗎?答案是有,而且很多。例如 Ruby Mine 是 JetBrains 做的 Ruby IDE ,功能相當豐富,在 Ruby 圈也相當知名,可以幫你重構、跑測試、除錯、找定義等,還有漂亮的 UI ,當然還有一個特點就是「貴」。其他如 Eclipse 、 NetBeans 也都可以用來開發 Ruby ,但對我這種不用滑鼠黨的黨工實在無法用這些畫面上到處是 Button 的 IDE 。
那身為菜鳥 Rubyist 該從哪款 IDE 下手呢? AWS Cloud9 也許是個不錯的選擇。也許你會問明明有這麼多老少咸宜的 IDE 了,為什麼還要特地用 Emacs 呢?如果你是追求漂亮 UI 或喜歡用滑鼠 Coding 的菜鳥工程師的話,你可以選擇繼續留在舒適圈,使用別人幫你設定好的工具,專心學習 Coding 。如果你已經是個算是有點成熟的工程師了,就應該嘗試看看捨棄滑鼠、脫離漂亮 UI 的束縛,追求更高的開發效率,為自己打造一個量身訂做的 IDE ,讓自己更有 Style 、更 Special 一點。
第一步
雖然我在 Part 1 提到了一些初始設定,但我們還是重新來一遍吧!
建立設定檔
為了方便我們採用 .emacs
單一檔案的方式來管理設定,到 $HOME
目錄底下建立檔案:
$ cd ~ $ emacs .emacs
初始設定
讓我們將煩人的 Welcome 畫面取消,在 ~/.emacs
裡面加入:
(setq inhibit-startup-message t)
Emacs 預設開啟的 *scratch*
Buffer 內會有一些預設的訊息在上面,同樣也可以把它取消掉,讓這個 Scratch 乾淨點:
(setq initial-scratch-message nil)
在視窗上面的選單也好煩,旁邊的捲軸也是,通通都可以把它給關掉:
(menu-bar-mode -1) (toggle-scroll-bar -1) (tool-bar-mode -1)
這樣畫面乾淨多了。
備份檔
Emacs 有一個非常佛心的功能,就是隨時會幫你備份正在編輯的檔案,當你的 Emacs 不小心因為某些事故被強制關閉了,就算檔案還沒儲存也不用擔心,Emacs 會幫你還原回來。那這些備份檔是被存在什麼地方呢? Emacs 預設會在與你編輯的檔案同一個目錄底下建立這些備份檔,而且還分為很多種。
例如當我新開一個檔案 hello.rb
,輸入一些字並儲存,切個 Buffer 再切回來,然後在輸入一些字但不儲存,之後到 Terminal 強制把 Emacs 關掉,這時候檢查目錄會像這樣:
drwxr-xr-x 2 mike mike 4096 Oct 19 20:19 ./ drwxr-xr-x 11 mike mike 4096 Oct 19 20:10 ../ -rw-r--r-- 1 mike mike 20 Oct 19 20:18 '#hello.rb#' -rw-r--r-- 1 mike mike 19 Oct 19 20:17 hello.rb -rw-r--r-- 1 mike mike 14 Oct 19 20:11 hello.rb~
Emacs 幫你額外生了兩個備份檔,而且還不幫你刪掉,這會讓我們的專案目錄底下多出一堆垃圾,幸好可以透過設定將這些備份檔的產生位置移動到其他地方,在 ~/.emacs
中加入:
(setq create-lockfiles nil) (setq backup-directory-alist `((".*" . ,temporary-file-directory))) (setq auto-save-file-name-transforms `((".*" ,temporary-file-directory t)))
接著重開 Emacs 後在試試同樣的步驟,檢查目錄就會發現 Emacs 已經不會自動幫你將備份檔生在跟正在編輯的檔案同一個目錄底下了,真是可喜可賀。
套件
在 Part 1 我提到過 Emacs 有自己的套件管理工具,我們可以懷著感恩的心透過它來使用許多高手大大們花費大量心力貢獻的套件。
MELPA
首先將 MELPA 這個 repository 加入到我們的 Emacs 中:
打開 ~/.emacs
將以下 Code 貼上:
(require 'package) (let* ((no-ssl (and (memq system-type '(windows-nt ms-dos)) (not (gnutls-available-p)))) (proto (if no-ssl "http" "https"))) ;; Comment/uncomment these two lines to enable/disable MELPA and MELPA Stable as desired (add-to-list 'package-archives (cons"melpa"(concat proto"://melpa.org/packages/")) t) ;;(add-to-list 'package-archives (cons"melpa-stable"(concat proto"://stable.melpa.org/packages/")) t) (when (< emacs-major-version 24) ;; For important compatibility libraries like cl-lib (add-to-list 'package-archives'("gnu" . (concat proto "://elpa.gnu.org/packages/"))))) (package-initialize)
接著重開 Emacs 並輸入指令:
<M-x> package-list-packages
稍微等個幾秒鐘,看看套件的來源是否有 melpa
,這樣就成功了。
Ruby Mode
ruby-mode
是 Emacs 內建的套件之一,這個套件提供最基本的編輯 Ruby Code 的支援,當我們在編輯副檔名為 .rb
的檔案時,Emacs 就會自動將主 Mode 設為 Ruby
,讓我們隨便打開一個 Ruby 檔案試試:
<C-x> <C-f> hello.rb
最基本的語法高亮、自動縮排等都已經預設支援了。但光是這個 ruby-mode
也只能讓 Emacs 看起來是個普通的 Editor ,讓我們來安裝其他套件,慢慢將不足的部份補齊吧!
界面樣式
我想讀者們已經忍受不了 Emacs 醜醜的界面了,Emacs 預設提供了幾種主題給你選,但是一樣都很醜,讓我們來透過套件來安裝一些比較潮的吧!
Dracula Theme
Dracula 是一個相當熱門的主題樣式,它支援各種知名的 Editor ,甚至連 Terminal 也支援,事不宜遲馬上來裝裝看!
<M-x> package-install dracula-theme
接著在 ~/.emacs
內加入:
(load-theme 'dracula t)
重開 Emacs ,畫面就會稍微變漂亮一點囉!
字型
大部分的了對於寫 Code 的視覺上影響最大的就是字型了, Emacs 當然也可以設定自己想要的字型與大小,首先選擇一個自己喜歡的 Font ,例如我喜歡 Input 這個字型,把它裝到電腦上後,在 ~/.emacs
中加入:
(set-face-attribute 'default nil :family "Input Mono" :height 150 :weight 'normal :width 'condensed)
這麼一來 Emacs 看起來就更現代化了。字的大小設定會依照每種不同的字型而有所不同,而如果要在 Coding 的時候即時挑整大小的話,可以透過 <C-x> -
與 <C-x> +
來調整縮小和放大。
游標
Emacs 預設的游標類型是粗粗的方塊,但現代一點的 Editor 大多都是細細的棒子,如果不習慣的話同樣可以來設定:
(setq-default cursor-type 'bar)
這樣有沒有好多了?
整理設定檔
目前我們的 .emacs
大概長這樣:
(require 'package) (let* ((no-ssl (and (memq system-type '(windows-nt ms-dos)) (not (gnutls-available-p)))) (proto (if no-ssl "http" "https"))) ;; Comment/uncomment these two lines to enable/disable MELPA and MELPA Stable as desired (add-to-list 'package-archives (cons"melpa"(concat proto"://melpa.org/packages/")) t) ;;(add-to-list 'package-archives (cons"melpa-stable"(concat proto"://stable.melpa.org/packages/")) t) (when (< emacs-major-version 24) ;; For important compatibility libraries like cl-lib (add-to-list 'package-archives'("gnu" . (concat proto "://elpa.gnu.org/packages/"))))) (package-initialize) ;; hello (custom-set-variables ;; custom-set-variables was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. '(package-selected-packages (quote (dracula-theme)))) (custom-set-faces ;; custom-set-faces was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. ) ;; Settings (setq inhibit-startup-message t) (setq initial-scratch-message nil) (menu-bar-mode -1) (toggle-scroll-bar -1) (tool-bar-mode -1) (setq create-lockfiles nil) (setq backup-directory-alist `((".*" . ,temporary-file-directory))) (setq auto-save-file-name-transforms `((".*" ,temporary-file-directory t))) (load-theme 'dracula t) (set-face-attribute 'default nil :family "Input Mono" :height 150 :weight 'normal :width 'condensed) (setq-default cursor-type 'bar)
自動補齊
在 Part 1 我們提到 company
、 ivy
、 counsel
、 swiper
這幾個強大的套件,先把它裝起來:
<M-x> package-install company <M-x> package-install ivy <M-x> package-install swiper <M-x> package-install counsel
接著在 ~/.emacs
中加入:
(global-company-mode) (ivy-mode 1)
Company Mode
company-mode 是個強大的文字自動補齊套件,與大部分現代 Editor 差不多,只要輸入一些幾個字就會幫你開出候補字選單,如下圖所示。
當選單出現後可以透過 <M-p>
與 <M-n>
來做上下選擇移動,之後按下 <C-m>
也就是 Enter
鍵決定,在最純 company-mode
的模式下通常會自動幫你搜尋你的 Buffer 之中曾經出現過的字來做候補,如果要進階一點如自動顯示 Function、Method 等等對於語言的支援的話可以在額外安裝針對特定語言的 company
套件,例如今天我想開發 Erlang ,就可以安裝 company-erlang
這個套件, Ruby 的話也有相關套件,會在之後提到。
Ivy
ivy
也是個自動補齊套件,應該說是一個框架,與 company-mode
不同的是, ivy
可以幫你為除了 Coding 之外如指令方面的輸入做自動補齊,我們可以試著按下 <M-x>
輸入指令看看,由於 ivy-mode
在 Emacs 有了出現在底下的選單,透過這個選單可以選擇候補指令、檔案等等的選擇,在輸入的同時還可只輸入片段的關鍵字,例如輸入 <M-x> fin fi
,ivy
就會幫你過濾出包含 fin fi
的所有指令。
搜尋
上面提到 ivy
可以幫你做到指令、檔案搜尋時的自動補齊,但最重要的是它提供了一個可以展開的置底界面,它也可以用來顯示搜尋的結果。
Counsel
counsel
是依賴 ivy
這個框架的套件,提供了許多 ivy
本身不包含的強大功能,可以用它來做整個專案的檔案及文字的搜尋等等。無論今天在使用什麼 Editor 或 IDE , 專案內的全域搜尋都跟專案的開發效率有著極大的影響,可以說是在專案開發時最常用到的功能之一,因此相當重要。
The Silver Searcher (ag)
在開始使用 counsel
之前必須先安裝 the silver searcher 又稱 ag
的這個東西, ag
是個類似 ack
或 grep
的指令,可以幫你做某個檔案或目錄下的文字搜尋,但 ag
的速度更快,與 git
的支援也比較好,因此也被許多 Editor 整合進去。
詳細安裝方式請參考 the silver searcher 的 GitHub 頁面。
專案內檔案搜尋
要對整個專案進行搜尋必須使用 counsel-projectile
這個 counsel
的擴充套件,先把它裝上:
<M-x> package-install counsel-projectile
確保 ivy-mode
是 enabled
的狀態,比較目前 Buffer 是在 Git 專案目錄底下,我們可以試著建立一個 Rails 專案來試試。
$ rails new hello $ cd hello $ emacs Gemfile
接著透過 counsel-projectile-find-file
這個指令來迅速地搜尋並打開 config/routes.rb
這個檔案,順便練習一下 ivy
的關鍵字搜尋。
<M-x> co pro fi fi <C-m> [hello] Find file: rou <C-m>
很神奇地就這樣把 config/routes.rb
給開起來了!
趕快來把 counsel-projectile-find-file
設個 Shortcut ,打開 ~/.emacs
加入:
(global-set-key (kbd "C-c f") 'counsel-projectile-find-file)
如此一來就可以很方便又快速地來開啟檔案囉!完全不需要打開一層又一層的目錄,在搭配強大的 ivy
支援的 Buffer 切換,不需要大多數 Editor 或 IDE 提供的側邊目錄樹或上面的 Tab bar 就可以在檔案之間來去自如了。
專案內文字搜尋
那我想要在整個專案裡面搜尋某幾個字呢?這時候就需要強大的 ag
支援了。counsel-projectile-ag
這個指令就能夠很神奇地即時幫你搜尋整個專案內的所有文字,而且會幫你略過在 .gitignore
清單內的檔案。例如我想要搜尋 Hello
這個 Module 是在哪裡定義的:
<M-x> cou pro ag <C-m> [hello] ag: mod hel <C-m>
哇!我的游標竟然 module Hello
的位置了!甚至我想搜尋 hello
這幾個字在專案裡面出現在哪些地方, counsel
也會即時地把所有結果列出來,透過 <C-p>
與 <C-n>
來上下選擇,瞬間就幫你開到指定的檔案的特定行數去,簡直太神奇了!
一樣趕快把它設個 Shortcut 。
(global-set-key (kbd "C-c s") 'counsel-projectile-ag)
要記得如果想要不分大小寫搜尋,就通通輸入小寫,一旦中間出現大寫字的話就會被認為要分大小寫搜尋。輸入空白鍵則表示零或多個任何文字,也可以利用正規表示法來尋找某個 Pattern 。
Buffer 內文字搜尋
Emacs 預設就有 search-forward
與 search-backward
這兩個指令讓你做文字搜尋了,但這樣還是有點不方便,這時就可以利用 swiper
這東西來加強整個 Buffer 內的文字搜尋。
直接透過指令 swiper
就可以看到利用 ivy
展開的選單,之後可以輸入要搜尋的文字,搜尋結果就會被詳細地列出來了,同樣可以透過上下鍵來進行選擇,並讓游標跟著移動。
例如我想要在 Gemfile
檔案內列出所有的 Gem 。
<M-x> swiper <C-m> Swiper: gem
這樣比起原生的內建搜尋還要更加視覺化,操作也更加方便。
小結
以上我們將使用 Emacs 進行開發必不可少的重要插件一一做了個介紹,當然實際的使用還是得參考每個插件的文件去了解,並經常使用來熟悉。本章篇幅較短,但一次提及太多恐怕無法消化,最重要的還是要多加練習。配合上述所提到的工具基本上已經能夠進行大部分的開發工作了,但身爲一個 IDE 恐怕還不夠格,因此我會在 Part 3 進一步地將開發 Ruby 以及 Rails 所需的相關插件以及在 Emacs 的 Git 操作等做進一步的詳細介紹,讓我們的 Emacs 真正躍升爲一個名副其實的 Ruby IDE。
TypeScript Design Pattern 系列 - Decorator Pattern
Decorator Pattern (修飾模式)是一種可以讓你有彈性的動態地擴充 class 的 design pattern ,一般我們要將一個 class 往下分的更細的話往往都要另外在建立更多的 class ,但如果要在往下分的種類太多,或有些子 class 有重疊的性質的話,此時用 Decorator Pattern 會相當合適。
繼續用武器的例子,假設有劍與刀兩種武器,而每個武器都可以有不同的屬性,例如火屬性或冰屬性等,也就是說可能會有火屬性的劍或刀,此時用一般的設計方式可能會變成:
abstract class Weapon { abstract getType(): string; getProperty(): string { return "normal"; } } class Sword extends Weapon { getType(): string { return "sword"; } } class Knife extends Weapon { getType(): string { return "knife"; } } class FireSword extends Sword { getProperty(): string { return "fire"; } } class FireKnife extends Knife { getProperty(): string { return "fire"; } } ...
只要屬性與武器種類愈來愈多,情況就會一發不可收拾,導致大量 class 被產生出來。
實做
// decorator/weaponDecorator.ts export abstract class Weapon { abstract getType(): string; getProperty(): string { return "normal"; } toString(): string { return `${this.getProperty()} ${this.getType()}`; } } export class Sword extends Weapon { getType(): string { return "sword"; } } export class Knife extends Weapon { getType(): string { return "knife"; } } abstract class WeaponDecorator extends Weapon { weapon: Weapon; constructor(weapon: Weapon) { super(); this.weapon = weapon; } getType(): string { return this.weapon.getType(); } getProperty(): string { return this.weapon.getProperty(); } } export class FireWeaponDecorator extends WeaponDecorator { getProperty(): string { return "fire"; } } export class IceWeaponDecorator extends WeaponDecorator { getProperty(): string { return "ice"; } }
以上實做了 WeaponDecorator
這個繼承自 Weapon
的 abstract class ,並往下分成 FireWeaponDecorator
與 IceWeaponDecorator
可分別讓武器賦予火與冰等屬性。只要將繼承自 Weapon
的 object 讓這些 decorator 做 decorate 的動作之後,就會根據 decorator 的不同而得到不同的屬性。
Demo
// decorator/demo.ts import { Weapon, Sword, Knife, FireWeaponDecorator, IceWeaponDecorator } from './weaponDecorator'; const normalSword: Weapon = new Sword(); const fireSword: Weapon = new FireWeaponDecorator(normalSword); const iceSword: Weapon = new IceWeaponDecorator(normalSword); console.log(normalSword.toString()); // normal sword console.log(fireSword.toString()); // fire sword console.log(iceSword.toString()); // ice sword const normalKnife: Weapon = new Knife(); const fireKnife: Weapon = new FireWeaponDecorator(normalKnife); console.log(normalKnife.toString()); // normal knife console.log(fireKnife.toString()); // fire knife
在 Ruby 中用 eval() 呼叫 eval()
許多有直譯器的語言都有 eval()
這個函數或者方法可以用,像是 Python 、 Common Lisp 、 Elixir 等,因為要進行直譯程式碼的話就必須有 REPL (Read–eval–print loop) 的功能,也就是先讀取(read)一段程式碼字串,在對這段字串做求值(eval)來得到執行結果,之後將這段結果列印(print)到畫面上的這整個過程的迴圈(loop),也就是說在我們使用像是 irb 或 iex 之類的 REPL 工具時,無時無刻都在呼叫 eval 。
eval 的用法
在 Ruby 之中, eval()
是一個 Kernel
module 底下的一個方法,也就是說已經被包含在 Object
的 class 裡,就像 puts()
一樣可以直接呼叫。試著使用 eval()
來呼叫 puts()
看看。
irb(main):001:0> eval('puts "Hello"') Hello => nil
我們將 puts "Hello"
當作參數來呼叫 eval()
,結果就跟直接呼叫 puts()
一樣,而剛剛也說過,在 REPL 中一直都在進行著 eval ,也就是說 eval('puts "Hello"')
這段字串本身也被當作參數來呼叫 eval()
,那我們再來嘗試看看多加一層 eval 。
irb(main):002:0> eval("eval('puts \"Hello\"')") Hello => nil
結果一樣,而這個過程中總共呼叫了 3 次 eval()
。
用 eval 實作迴圈
所以 eval()
可以呼叫自己,這聽起來怎那麼像遞迴?既然是遞迴,那應該可以拿來實做個迴圈吧?
首先來驗證看看 eval()
到底能不能當遞迴用。
irb(main):001:0> f = "eval(f)" => "eval(f)" irb(main):002:0> eval(f) Traceback (most recent call last): 16: from (eval):1 15: from (eval):1:in `eval' 14: from (eval):1 13: from (eval):1:in `eval' 12: from (eval):1 11: from (eval):1:in `eval' 10: from (eval):1 9: from (eval):1:in `eval' 8: from (eval):1 7: from (eval):1:in `eval' 6: from (eval):1 5: from (eval):1:in `eval' 4: from (eval):1 3: from (eval):1:in `eval' 2: from (eval):1 1: from (irb):2:in `eval' SystemStackError (stack level too deep)
當我們 eval f
這個字串後,eval(f)
又會在被呼叫一次,然後就不斷迴圈的進行 eval(f)
導致 stack overflow 了,跟遞迴一模一樣。利用這個特性來實做一個 a 加到 b 的加總函數看看。
def sum(a, b) func = <<STR if a >= b b else t = a a += 1 t + eval(func) end STR eval(func) end puts sum(1, 100) # 5050
Self Printing Program
使用 eval()
也可以很簡單地 self printing ,所謂的 self printing 指的是一段程式碼的列印結果就是這段程式碼,例如 puts "Hello"
執行完顯示的如果一樣是 puts "Hello"
這樣一個字串的話,那就是 self printing program 。
eval(f = %[puts 'eval(f = %[' + f + '])']) # eval(f = %[puts 'eval(f = %[' + f + '])'])
這段程式碼巧妙地利用了 puts()
與 eval()
同樣接收一個字串參數但 puts()
只會直接將字串列印出來,而 eval()
則是對字串進行求值的特性,其中 f
扮演了很重要的角色。最外層的 eval
會對 f
進行求值,進而呼叫了 puts
,而 puts
所接收到的又是將整行程式碼重新組合而成的字串。
結論
以上介紹了 eval()
的一些有趣的用法,但僅限於私底下玩玩,eval()
還有許多用法以及值得深入討論的部份,包含在 meta programming 的實際應用等,但在實務上還是盡量避免使用比較好。
TypeScript Design Pattern 系列 - Abstract Factory Pattern
Abstract Factory Pattern (抽象工廠模式)是在 Factory Method Pattern 再往上抽象化一層的 design pattern ,亦即將工廠再進行一次抽象,進而產生出相同介面的不同工廠,來生產出不同的物件。
例如在之前的文章中提到的武器工廠的例子,雖然我們可以透過武器工廠生產出長劍、短劍、小刀等,但如果生產這些武器的工廠有好幾家,且生產出來的武器品質各有好壞的話。
實做
// abstract_factory/weaponFactory.ts export abstract class Weapon { weaponType: string; length: number; quality: string; constructor(weaponType: string, length: number, quality: string) { this.weaponType = weaponType; this.length = length; this.quality = quality; } getInfo(): string { return `Type: ${this.weaponType} - ${this.length} cm - ${this.quality} quality`; } } class Sword extends Weapon { constructor(length: number, quality: string) { super("Sword", length, quality); } } class Knife extends Weapon { constructor(length: number, quality: string) { super("Knife", length, quality); } } export interface WeaponFactory { createLongSword(): Sword; createShortSword(): Sword; createKnife(): Knife; } export class GoodWeaponFactory implements WeaponFactory { createLongSword(): Sword { return new Sword(150, "good"); } createShortSword(): Sword { return new Sword(100, "good"); } createKnife(): Knife { return new Knife(50, "good"); } } export class BadWeaponFactory implements WeaponFactory { createLongSword(): Sword { return new Sword(150, "bad"); } createShortSword(): Sword { return new Sword(100, "bad"); } createKnife(): Knife { return new Knife(50, "bad"); } }
以上將 WeaponFactory
變成一個 interface ,之後分別由 GoodWeaponFactory
與 BadWeaponFactory
實做,同樣有 createLongSword()
等方法,但產生出的 Sword
的品質則會有好壞之分。
Demo
// abstract_factory/demo.ts import { Weapon, WeaponFactory, GoodWeaponFactory, BadWeaponFactory } from './weaponFactory'; const goodFactory: WeaponFactory = new GoodWeaponFactory(); const badFactory: WeaponFactory = new BadWeaponFactory(); const goodLongSword: Weapon = goodFactory.createLongSword(); const goodShortSword: Weapon = goodFactory.createShortSword(); const goodKnife: Weapon = goodFactory.createKnife(); console.log(goodLongSword.getInfo()); // Type: Sword - 150 cm - good quality console.log(goodShortSword.getInfo()); // Type: Sword - 100 cm - good quality console.log(goodKnife.getInfo()); // Type: Knife - 50 cm - good quality const badLongSword: Weapon = badFactory.createLongSword(); const badShortSword: Weapon = badFactory.createShortSword(); const badKnife: Weapon = badFactory.createKnife(); console.log(badLongSword.getInfo()); // Type: Sword - 150 cm - bad quality console.log(badShortSword.getInfo()); // Type: Sword - 100 cm - bad quality console.log(badKnife.getInfo()); // Type: Knife - 50 cm - bad quality
Emacs 入坑引導 - 打造自己的 Ruby IDE - Part 1
前言
工欲善其事,必先利其器,出色的工匠都有自己熟練的工具;傑出的戰士都有自己拿手的兵器;優秀的碼農都有自己慣用的 Editor。世間 Editor 千百種,Atom、VSCode、Sublime、Notepad++(?)... 等皆是現代碼農之所好。但萬變不離其宗,每個 Editor 都有相似之處,追求的都是更便捷的文字編輯功能,讓程式碼更容易閱讀、修改。選好一個 Editor 是躍升專業碼農的第一步,挑選適合自己的 Editor 就像挑老婆一樣,必須用心陪伴,才能了解其本質與內含,進一步決定能否成為自己一生的伴侶,而非從表面上判斷。現代大多數 Editor 都有漂亮的包裝,功能也相當完整,但有二個從上古時代流傳至今,其外表看似難以接近,但仍然頂立於眾 Editor 之上,被眾多碼農所愛,其名為 Emacs 與 Vim。
Emacs 和 Vim 之所以在頂尖碼農們之中無人不知、無人不曉,甚至被譽為「神之編輯器」就在於其極為強大的功能,且各自有其特點與不同的本質,也因此導致兩家 Editor 各自形成派別,相互歧視,因而發生了繼第二次世界大戰之後最慘烈的戰爭,其名為「Editor war」。
這兩家 Editor 共同的特點就是難以入門,因為與其他 Editor 不同的奇特 Key binding 讓許多新手碼農怯而遠之,就算努力點成功越過這道牆而開始入門探索,也必須花許多時間熟練,才能夠真正掌握並充分運用它讓自己更幸福地 Coding。
從其他現代的 Editor 甚至 IDE 而轉戰 Emacs 或 Vim 是一件極其艱難的事,而從 Emacs 和 Vim 之中做抉擇也令人困惑而猶豫不決,以下將一步步介紹 Emacs 的基礎,並打造出自己的 Ruby IDE。
Best ruby editors, 圖片來源:https://www.sitepoint.com/editor-rubyists-use/
Emacs V.S. Vim
網路上流傳著這麼一張圖: Editor learning curves
上圖是各種 Editor 的學習曲線,所謂學習曲線根據維基百科定義:
學習曲線是對某種活動或工具的學習速率(平均情況)的圖形化表示。一般來說,剛開始時掌握信息的速率曲線最為陡峭,之後則逐漸變得平緩,這表明之後的學習過程中對新信息的掌握速率會越來越慢。
從上圖可知,大多數 Editor 在學習初期到後期都是呈現一個平滑陡峭的曲線,之後持續穩定,與維基上所說的一般情況相符。這表示剛開始會需要學比較多東西,學習的速度也會比較慢,但一段時間之後就大致可以掌握,也比較難學到新的東西了。
但其中有兩個曲線特別出眾,Vim 是一條從頭到尾都在高處的直線,表示入門的時候學習難度極高,但就算學了一陣子之後難度依舊不便,意思是 Vim 提供許多東西可以學,要完全熟練 Vim 是件極為困難的事。
另外一個就是 Emacs,可以看到 Emacs 的曲線是一條從原點出發的棒棒糖,表示入門的時候學習難度較低,到了中期掌握了一些基本之後難度會越來越高,但永遠不會有後期的到來,因為你已經被 Emacs 廣大的世界給吞噬了。
在這兩個奇耙的 Editor 之中做選擇,導致了兩派使用者相互主觀地評論對方,結果只會導致新手碼農遭到洗腦。因此這邊會以客觀的方式來評論兩家的優缺點。
比較
執行環境
首先在執行環境上,Emacs 與 Vim 都是從幾十年前電腦還沒有 GUI 就被開發出來的,因此都能直接在 Terminal 上執行,若要在 GUI 上執行,GNU Emacs 本身就提供 GUI 的環境,Vim 則可以透過 GVim 來達成。使用 Terminal 或 GUI 取決於個人的需求,通常在 Emacs 會推薦用 GUI,因為 GUI 可提供的功能更多,例如顯示圖片、網頁等等。
開啟時間
許多人支持 Vim 所主張的原因之一就是其開啟檔案的速度快,反而批評 Emacs 慢,但從客觀的角度上看,這點完全沒有比較的必要,因為這取決各自的使用方式,在 Emacs 可以完全無視初次開啟時的時間花費,因為 Emacs 的開啟通常是隨著電腦開機時一起啟動的,而且只會啟動一次,而開啟檔案的時間幾乎沒差(瞬間)。
本質定位
Vim 與 Emacs 有著本質上的不同,Vim 本身的定位就是徹徹底底的 Editor,而常常有主觀的 Vimer 笑說:Emacs 是個好的 OS,只差沒有好的 Editor,這句話前半段有理,後半段可無視。也有人說: 對我來說 Emacs 才是我的 OS,Linux 只不過是用來啟動 Emacs 的 Bootloader,會有這些言論的原因在於 Emacs 本身提供的功能包山包海,例如除了 Editor 本身的功能外,還可看圖片、聊天、收發信件、瀏覽網頁(雖然不好用?)...... 等,也提供專案管理、git 操作(像 GitKraken 那樣)、Shell,甚至要將它變成一個 Ruby IDE 也行。總而言之你可以透過 Emacs 使用許多平時常用的功能(除了瀏覽器:)),Editor 比較像是剛好在 Emacs 這個 OS 裡面的功能之一而已,甚至已經許多套件都能將 Vim 實現在 Emacs 內了。
擴充語言
Vim 的擴充語言是 Vim script,而 Emacs 是 Emacs Lisp,先不論效率或學習難易等,兩者最大的差別其實在於,Vim script 只是 Vim 的擴充用的語言,Vim 本身大多還是用 C 實做:而 Emacs 本身大多都是用 Emacs Lisp 寫成的。在 Vim 中 Vim script 與 C 的比例大約是 1:1,而在 Emacs 中 Emacs Lisp 與 C 的比例大約是 4:1,很明顯的 Vim 相較之下比較依賴於 C。論功能面無論主觀客觀,在事實上 Emacs Lisp 可達到的功能都是比較多的,你可以找到在 Emacs 裡面實作的 Vim,但你找不到在 Vim 裡面實作的 Emacs,不過單論寫 Editor 方面的功能,兩者提供的功能都差不多。
按鍵
無論使用 Vim 或 Emacs 你都不需要動用到滑鼠,若你在 coding 的時候使用滑鼠,會導致你的手需要在鍵盤與滑鼠之間移動,導致效率下降。既然不需要滑鼠,那麼鍵盤上的按鍵配置就變得相當重要。Vim 與 Emacs 最大的差異就是彼此不同的按鍵配置,且不像其他如 Atom 與 VSCode 之間快捷鍵的不同,Vim 與 Emacs 在根本上就是兩個世界。Vim 支援 Mode 的切換,在 Normal mode 的時候可以按 H
、J
、K
、L
等來移動游標,按下 I
可以切換至 Insert mode,在 Insert mode 可以直接輸入文字,也就是說在不同 Mode 會有不一樣的按鍵組合,所以可以在同一個按鍵上同時達到不同的功能,也不影響輸入。而在 Emacs 中由於沒有方便的 Normal mode、Insert mode 等,因此幾乎執行任何動作時都會一起按下 Ctrl
,例如 Ctrl + F
就是游標向右一格,但要輸入文字時就可以直接輸入。由於按下 Ctrl
的通常是用小指,所以常常有人會覺得用 Emacs 久了小指會受傷,這個筆者也認了,不過能到達受傷的程度至少要用個幾十年吧?
語言支援
Vim 與 Emacs 對大多數的語言都有支援,就算沒支援也可以再擴充。對語言的支援包含語法高亮、語法檢查、錯字檢查、自動補齊...... 等等,這對大多數 Editor 都是有支援的。
結論
兩種 Editor 各有特色,也有許多共通的特點,選擇那一邊完全取決於個人的嗜好,不過若能將兩家 Editor 的特點融合在一起,那就沒什麼衝突了。幸好透過 Emacs 強大的擴充功能,的確已經有許多大神在 Emacs 裡面加入了 Vim 強大的按鍵與 Mode,讓你可以同時使用 Vim 方便的按鍵組合與 Emacs 大量功能強大的指令,如此一來就能平息持續多年的 Editor war 了。
安裝
以上講了這麼多 Emacs 的特色,接下來就開始讓好奇又興奮的各位碼農們排隊入坑吧!
Emacs 在大多數 OS 都有支援,安裝方式也很簡單:
MacOS
brew install emacs
Linux
Debian/Ubuntu
apt install emacs
Red Hat/Centos
yum install emacs
Arch/Manjaro
pacman -S emacs
BSD
FreeBSD
pkg_add -r emacs
OpenBSD
pkg_add -i emacs
Windows
Windows 版本可以在這邊下載: http://ftp.gnu.org/pub/gnu/emacs/windows/
也可以透過 MSYS2 安裝:
pacman -S mingw-w64-x86_64-emacs
也可上 GNU Emacs 官網 直接下載 Source Code 來安裝。
執行
Emacs 跟其他 Editor 一樣透過可 Command line 開啟檔案:
emacs <PATH>
Emacs 的設定檔通常存放在 $HOME
底下的 .emacs.d
資料夾裡面,或者是 .emacs
檔案本身,開啟 Emacs 的同時就會自動載入設定。當不需要使用這些設定檔,想要直接開啟乾淨的 Emacs 的話,可在執行時加入 -q
參數:
emacs -q <PATH>
嚴格來講
.emacs
或.emacs.d
並不是設定檔,而是像.bashrc
之類的在開啟階段先執行的檔案,裡面寫的是真正的 code。
emacs -nw
通常使用 Emacs 一段時間後,就會開始安裝許多套件,一旦套件數量愈來愈多,直接開啟 Emacs 的時間就會增加,因此大多 Emacer 都不會將 Emacs 關閉,而是選擇活在 Emacs 裡,直到電腦重開。但是當我們只是想要在其他 Terminal 上修改檔案,或者開啟新的專案時,還是會想要在 Terminal 上下指令開啟檔案。
Emacs 本身提供 Daemon 模式,可以將 Emacs 運行於背景,當要連接這個 Daemon 可以透過 emacsclient
指令:
emacs --daemon // 開啟 Emacs daemon
emacsclient <PATH>
使用 emacsclient
開啟檔案不需要讀取套件及設定檔,因此幾乎沒有等待時間,可瞬間開啟檔案。emacsclient
預設直接執行在 Terminal 上,如果要在 GUI 執行可加上 -c
參數:
emacsclient -c <PATH>
emacsclient -e "(kill-emacs)"
-e
參數跟 ruby -e
的作用一樣(eval),可以直接執行一段 Code,在 Emacs 就是執行 Emacs Lisp(簡稱 elisp),kill-emacs
是 elisp 的內建 Function,而用括號包起來是 Lisp 呼叫 Function 的方式。
第一次打開 Emacs 可能會長這樣,
這個畫面看起來有點醜,不過之後我們就會讓它面目一新囉。
初始設定
如果直接開啟 Emacs 就會出現上圖所示的預設畫面,但如果在開啟的時候同時給一個路徑:
emacs hello.rb
會長這樣,
如圖所示,Emacs 會幫你開啟二個 window,上面是你現在要編輯的檔案,下面是剛剛出現過的預設畫面。但我們不想要每次從 Terminal 開檔都跑出煩人的預設畫面,接下來我們就可以來更改一下 Emacs 的設定檔讓它消失!
初始化檔案
Emacs 的自訂設定通常放在 ~/.emacs
檔案內或者 ~/.emacs.d/
資料夾裡,不過 Emacs 預設並不會自動生成 .emacs
檔。
若想要將設定分的比較細一點,就可以用 .emacs.d/
資料夾來存放設定,用 .emacs.d/
的話預設會先讀取 .emacs.d/init.el
這個檔案。
Emacs 預設的讀取順序是 ~/.emacs
,~/.emacs.el
,~/.emacs.d/init.el
,筆者推薦用 .emacs.d/
來管理設定,但這邊為了簡潔就先用 ~/emacs
。
取消預設畫面
要讓預設畫面消失,必須先打開 Emacs 的設定檔 ~/.emacs
,這檔案剛開始可能不存在,就直接建立一個:
emacs ~/.emacs
之後在這個檔案加入下列 elisp 程式碼:
;; ~/.emacs (setq inhibit-startup-message t)
之後重開 Emacs 就只會看到開啟的檔案了。
setq
是指派變數 (assign) 的 Function,t
和nil
分別指true
和false
,所以(setq inhibit-startup-message t)
就是將inhibit-startup-message
這個變數的值設為true
。
內建教學
Emacs 提供內建的教學說明文件,在剛剛的預設畫面上即可看到 Emacs Tutorial
的選項,新手建議先照這份教學跑完一遍,就能大致掌握 Emacs 的用法囉!
基本 Key binding
進入 Emacs 的世界後,我們就要開始捨棄掉平時常用的幾個按鍵,包含 Up
、Down
、Left
、Right
、Home
、End
、Delete
、Backspace
、Tab
、Enter
等等,這些處在邊邊角角的鍵只會導致雙手大幅移動,降低打字效率。
那在 Emacs 要怎麼達到這些按鍵的功能,又不需要使用到這些鍵呢?
Emacs | 對應鍵 | 功能 |
---|---|---|
C-p |
Up |
游標往上一格 |
C-n |
Down |
游標往下一格 |
C-b |
Left |
游標往後一格 |
C-f |
Right |
游標往前一格 |
C-a |
Home |
游標移到最前面 |
C-e |
End |
游標移到最後面 |
C-d |
Delete |
刪掉後面一個字 |
C-h |
Backspace |
刪掉前面一個字 |
C-i |
Tab |
縮排 |
C-m |
Enter |
換行、確定 |
C
=Ctrl
M
=Alt
(註:C-h
在 Emacs 預設是 help
,之後我們會將它變成刪除鍵)
上表一一對應在 Emacs 內與其他常用鍵達到同樣功能的按鍵組合,C
表示 Ctrl
鍵,所以 C-p
就是按住 Ctrl
鍵後在同時按下 p
鍵。
在 Emacs 幾乎任何動作都脫離不了 Ctrl
與 Alt
鍵,在 Emacs 官方文件中分別將這兩個鍵用 C
與 M
來表示,因此之後我也會用這兩個字母表示。
以上組合必須經常練習才會記在身體裡,也就是某人所提倡的肌肉記憶。但為何 Emacs 當初設計時要將這些相關連的鍵定義在這些鍵上呢?仔細觀察一下就能發現這些鍵在定義的時候其實是根據每個功能的英文決定的,如 C-p
是 Previous
,C-b
是 Back
,C-i
是 Indent
等。
熟練這些按鍵還有一個好處是在幾乎任何類 Unix 作業系統中提供的 Terminal 都支援這些組合,不信邪可自己打開 Terminal 試試。
有些 Unix 配置的鍵盤甚至直接將方向鍵移除,如 HHKB 等,原因就是使用 Emacs 的 Key binding 的話根本用不到這些鍵。
上述按鍵組合只不過是 Emacs 的一小部份而已,以下我再列出幾個常用的:
Emacs | 功能 | 說明 |
---|---|---|
C-v |
往下一頁 | |
M-v |
往上一頁 | |
C-l |
維持游標位置並重新定位畫面 | 重複執行的話會分別在 top、center、bottom 的位置作切換 |
C-s |
往下搜尋文字 | |
C-r |
往上搜尋文字 | |
C-g |
離開指令 | 遇到指令執行不順可先嘗試按按這個來結束指令執行 |
C-o |
換行但不移動游標 | |
C-k |
殺掉游標後到換行字元前的文字 | |
C-w |
殺掉目前選取的文字 | |
C-y |
貼上保存在 kill ring 的文字 | 通常搭配 C-k 或 C-w 使用,來達成剪下貼上等功能 |
C-b |
切換 Buffers | |
C-x k |
殺掉 Buffer | |
M-< |
移動游標到 Buffer 最前面 | |
M-> |
移動游標到 Buffer 最後面 | |
C-x C-s |
存檔 | |
C-x C-c |
關閉 Emacs |
C
=Ctrl
M
=Alt
Kill Ring是 Emacs 特有的像是剪貼簿的列表,它會將你上次透過像是
C-k
或C-w
所 Kill 掉的字串存進去,之後可以利用C-y
將它貼回 Buffer。C-y
預設會取用最後存入的字串,如果想要貼回 Kill Ring 中其中一段字串,可以重複按M-y
來選擇。
指令
Emacs 預設就有 2812 個指令,筆者使用的 Emacs 有 7067 個指令,若使用 Spacemacs 這樣的 Starter Kit 甚至可達好幾萬個,這麼多的指令根本沒辦法一一用按鍵組合來對應。Emacs 預設有 1353 個按鍵組合,但我們平常根本用不到那麼多,自然不會去記住這麼多的組合,那當我們想要使用某個指令的時候但不知道按鍵時,就可以直接輸入指令。
使用指令
在 Emacs 可以用 M-x
來輸入指令,例如我想要使用 help
的指令,可以輸入 M-x help
。
什麼是指令?
Emacs 的每個指令都是一個用 elisp 寫的 Function,例如我們可以隨便開一個檔案,在空白處輸入:
(help)
在最後一個括號後面按 C-x C-e
來執行,這樣就會呼叫 help
這個 Function。
我們也可以試著定義一個指令,用 elisp 寫一個在游標後插入 Hello, World!
字串的 Function:
(defun hello () (insert "Hello, World!"))
同樣在最後一個括號後按 C-x C-e
執行後,就可以輸入:
(hello)
你的畫面上就會出現 Hello, World!
這串字了。
但是這樣還不算是個 Emacs 指令,必須讓這個 Function 能夠用 M-x
來執行,讓我們將 hello
的定義內加入 (interactive)
:
(defun hello () (interactive) (insert "Hello, World!"))
這麼一來就可以用 M-x
來找到 hello
這個 Function 了。
按鍵綁定
Emacs 提供許多 Function 可以幫助將指令綁定在自訂的按鍵上,例如要將 C-h
與 往後刪除一個字元 (delete-backward-char)的功能綁定的話,我們可以在 .emacs
檔案中加入以下設定:
(global-set-key (kbd "C-h") 'delete-backward-char)
重新打開 Emacs 即可。
global-set-key
會將指定的按鍵與所給的指令綁定在全域範圍內,也就是說不管在哪個 Buffer 下都會生效,當然 Emacs 也提供在特定 Buffer 或 Mode 中的按鍵綁定,但在本章中先暫且略過。
安裝套件
在 Emacs 有大量套件可以直接安裝,透過這些套件我們可以快速地為自己的 Emacs 做各種客製化。Emacs 預設的 repository 是使用 GNU 官方的 ELPA,不過 ELPA 的套件不多,所以通常都會利用MELPA 來取得更多套件。
套件列表
要列出所有可安裝與已安裝的套件可以透過指令 package-list-packages
,如果想要安裝列表上的某個套件,可以將游標指到套件那行再按下 I
,前面就會多出一個 I
的標記,之後按 X
就能開始執行下載安裝,如果想取消可以按 U
,標記就會消失。
加入 MELPA
要加入 MELPA 上的套件必須先在 .emacs
裡加入一些設定:
(require 'package) (let* ((no-ssl (and (memq system-type '(windows-nt ms-dos)) (not (gnutls-available-p)))) (proto (if no-ssl "http" "https"))) ;; Comment/uncomment these two lines to enable/disable MELPA and MELPA Stable as desired (add-to-list 'package-archives (cons"melpa"(concat proto"://melpa.org/packages/")) t) ;;(add-to-list 'package-archives (cons"melpa-stable"(concat proto"://stable.melpa.org/packages/")) t) (when (< emacs-major-version 24) ;; For important compatibility libraries like cl-lib (add-to-list 'package-archives'("gnu" . (concat proto "://elpa.gnu.org/packages/"))))) (package-initialize)
詳細可參考Getting Started。
之後重開 Emacs 並執行 package-list-packages
之後,Emacs 會先將 ELPA 的套件讀取進來,再稍等一下就會將 MELPA 的套件也讀進來。由於大部分常用的套件都在 MELPA 上,所以這個動作基本是必要的。
MELPA 的套件可以直接到 MELPA 官網 上查看。
建議安裝套件
以下列出一些筆者建議安裝的套件列表,詳細以及打造 Ruby IDE 的部份將會在 Part 2 作介紹。
套件 | 功能 | 說明 |
---|---|---|
company | 自動補齊 | 需要根據不同語言來額外安裝其他 company 的套件。 |
projectile | 專案管理 | |
ivy | 自動補齊 | 與 company 不同的是自動補齊指令輸入等比較通用型的部份。 |
counsel | 強化指令輸入 | |
counsel-projectile | 強化 projectile | |
swiper | 強化文字搜尋 | |
avy | 強化游標移動 | |
undo-tree | 強化 undo、redo | |
multiple-cursors | 多游標 | |
magit | Git 管理操作 | |
which-key | 顯示 Key Map |
如果要讓 Emacs 支援像 Python、Elixir 等語言,可以安裝 python-mode
、elixir-mode
等套件,根據自己的需求來選擇。
Buffers
Buffers扮演著重要的角色,當開啟檔案 (find-file
) 的時候 Emacs 會自動產生該檔案的 Buffer,讓你可以透過 C-x b
(switch-to-buffer
)來切換 Buffer,如此可以保留每個檔案的 Session,不需要關閉檔案。每個 Buffer 都會有一個名字,當執行 switch-to-buffer
時給了一個不存在的 Buffer 名,Emacs 就會開啟一個新的 Buffer,Buffer 不一定是對應到一個檔案,可以只用來顯示結果、訊息,也可以顯示圖片,或者執行 Shell。
管理 Buffers
Emacs 預設將 C-x C-b
綁定到 list-buffers
指令,該指令會打開 Emacs 預設的 Buffer Menu,不過筆者推薦將這個按鍵重新綁定到 ibuffer
指令,Ibuffer 是進階版的 Buffer Menu,能讓你對 buffers 作分類管理。
(global-set-key (kbd "C-x C-b") 'ibuffer)
Frames
Frames是 Emacs 的 GUI 視窗,Emacs 可以產生出多個視窗,但是共用同一個 daemon。Frames 是 GUI 版的 Emacs 才能使用的,在開啟 Emacs 時就會產生一個預設的 Frame,之後可以在這個 Frame 再產生新的 Frame,需要注意的是,在同一棵 Frame 的樹的其中一個節點用 C-x C-c
關閉 Emacs 會導致整棵樹的所有 Frame 也一起被關閉。
以下列出操作 Frames 的常用相關按鍵組合:
Emacs | 功能 |
---|---|
C-x 5 0 |
關閉目前的 Frame |
C-x 5 1 |
關閉目前以外的所有 Frame |
C-x 5 2 |
建立新的 Frame |
C-x 5 o |
切換至其他 Frame |
Windows
Windows是在同一個 Frame 中可在切割出的視窗,我們可以在同一個畫面再水平或垂直地切割出多個視窗,顯示不同的 Buffer,並且在其中進行切換,這樣就不須使用到多個 Frame 來分割畫面。
以下列出操作 Windows 的常用相關按鍵組合:
Emacs | 功能 |
---|---|
C-x 0 |
關閉目前的 Window |
C-x 1 |
關閉目前以外的所有 Window |
C-x 2 |
垂直切割目前的 Window |
C-x 3 |
水平切割目前的 Window |
C-x o |
切換至其他 Window |
Shell
在 Emacs 有多種執行 Shell 的方式,其中一種是直接執行系統預設的 Shell 在 Emacs 的 Buffer 中,另一種是執行 Emacs 特有的 EShell,EShell 是透過 Emacs Lisp 實作的 Shell,提供像 Bash 等大多數 Shell 可執行的指令,也可以直接執行 Emacs Lisp,對於 Emacs 的進階使用者來說是個相當方便的 Shell。
執行 Shell 的 Buffer 只要輸入指令 M-x shell
即可。若只要執行一個指令卻又不想開啟新的 Shell Buffer,可以按 M-!
後輸入指令,執行結果會透過 Buffer 來顯示。
執行 EShell 同樣輸入指令 M-x eshell
即可。可以試試看在 EShell 中使用 ls
等基本指令,這些指令都是用 Emacs Lisp 實作,但用法跟 Bash 差不多。另外也可以試試看執行 Emacs Lisp 的 Function:(message "hello")
,結果會跟 echo "hello"
一樣,但你不能執行 (echo "hello")
。
在 Emacs 中使用 Shell 的好處之一在於可以在 Shell 的 Buffer 中移動游標,就像在一般的檔案中進行複製貼上等文字操作,而不需要像一般 Terminal 那樣使用滑鼠來選取文字。
Remote Files
Remote files讓 Emacs 可以透過 SSH 等方法來操作遠端機器上的檔案或執行指令等。使用時只需要透過 C-x C-f
(find-file
)就像操作本機檔案一樣,但是需要在檔案路徑前加上遠端機器位置與使用者名稱等資訊:
/method:host:filename /method:user@host:filename /method:user@host#port:filename
例如想要用 SSH 在 myhost
機器上透過使用者 myuser
來存取 ~/hello.rb
檔案的話可以輸入:
/ssh:myuser@myhost:~/hello.rb
這樣就可以遠端存取 hello.rb
了,操作檔案時會在自己的機器上,但存檔後會直接存在遠端機器上,不用擔心網路速度導致編輯檔案時的延遲。
許多人覺得 Vim 最大的優點在於它在許多機器上都是內建好的,而且不用吃太多效能。但如果我們要存取的機器是可以用網路來遠端存取的話,甚至不需要在機器上安裝任何 Editor,只要透過 Emacs 就能輕易地在其中操作檔案。
小結
Part 1 的部份我們簡介了 Emacs 與 Vim 的比較、Emacs 的安裝、按鍵操作、套件以及 Buffers 等等基本元素,了解這些並好好練習,掌握 Emacs 的基本操作之後,才能更加深入。在 Part 2 會開始介紹如何透過各種套件以及自己撰寫的 Emacs Lisp 來打造自己專屬的 Ruby IDE。
Starter Kits
網路上有許多 Emacs 的 Starter kit 可以使用,這些都是已經幫你設定好的 .emacs.d
,不過通常會提供許多選項讓你可以在其中選擇需要的功能。剛入門其實也可以先玩玩看這些 Starter kit,不但省去許多設定的麻煩,熟了之後還可以參考這些高手們的設定來從頭寫一份屬於自己的。
以下是比較知名的幾個Starter Kits:
- Spacemacs
- Prelude
- purcell emacs.d
- magnars
- Emacs Starter
- oh-my-emacs
- Better Defaults
- Graphene
- ohai-emacs
- ergoemacs-mode
其中比較特別的是 Spacemacs,如果你是從 Vim 轉到 Emacs 的高手,或者是完全懶得設定的菜鳥,都強烈推薦可以試試。
切換設定檔
如果想要嘗試多種 Starter kit,然後又不想要複製來複製去的,可以將這些設定檔的專案通通存在其他地方,然後使用 ln -s
指令來將這些資料夾連結到 ~/.emacs.d
,這樣就可以隨時進行切換了。
相關文件連結
如果對 Emacs 已經產生極大的興趣,想要跟深入了解,可以參考以下幾個網站:
TypeScript Design Pattern 系列 - Factory Method Pattern
Factory Method Pattern (工廠方法模式)顧名思義是一種在 OOP 實現了所謂的工廠運作概念的一種 design pattern 。
工廠是一種用來建立 object 的實體,與直接使用 new
產生 object 不同的是工廠方法是一種抽象的產生 object 的建構方法。例如有一家武器工廠專門生產長劍、短劍、小刀等武器,你只需要透過這個工廠所提供的方法就能夠產生這些武器。
實做
// factory_method/weaponFactory.ts export abstract class Weapon { weaponType: string; length: number; constructor(weaponType: string, length: number) { this.weaponType = weaponType; this.length = length; } getInfo(): string { return `Type: ${this.weaponType} - ${this.length} cm`; } } class Sword extends Weapon { constructor(length: number) { super("Sword", length); } } class Knife extends Weapon { constructor(length: number) { super("Knife", length); } } export class WeaponFactory { createLongSword(): Sword { return new Sword(150); } createShortSword(): Sword { return new Sword(100); } createKnife(): Knife { return new Knife(50); } }
上面定義了 Weapon
這個抽象類別,由 Sword
和 Knife
來實做,而 WeaponFactory
用來產生各種類型與不同長度的 Sword
與 Knife
,這樣我們只需要知道 WeaponFactory
的 createShortSword()
可以產生出固定長度為 100 的 Sword
,而不需要管 Sword
是如何被產生的。
Demo
// factory_method/demo.ts import { Weapon, WeaponFactory } from './weaponFactory'; const factory: WeaponFactory = new WeaponFactory(); const longSword: Weapon = factory.createLongSword(); const shortSword: Weapon = factory.createShortSword(); const knife: Weapon = factory.createKnife(); console.log(longSword.getInfo()); // Type: Sword - 150 cm console.log(shortSword.getInfo()); // Type: Sword - 100 cm console.log(knife.getInfo()); // Type: Knife - 50 cm
以上參考自 https://github.com/torokmark/design_patterns_in_typescript/tree/master/factory_method