本文翻译自 http://doc.norang.ca/org-mode.html ,原文作者为Bernt Hansen 。由于原文较长,因此会分多篇文章来发布。转载请标记出处。

Org-mode, 用文本文件管理日常(二)

Org-mode, 用文本文件管理日常(三)

Org-mode, 用文本文件管理日常(四)

Org-mode, 用文本文件管理日常(五)

Org-mode, 用文本文件管理日常(六)

Org-mode, 用文本文件管理日常(七)

Org-mode, 用文本文件管理日常(八)

Org-mode, 用文本文件管理日常(九)

Org-mode, 用文本文件管理日常(十)

Org-mode, 用文本文件管理日常(十一)

Org-mode, 用文本文件管理日常(十二)

Org-mode, 用文本文件管理日常(十三)

Org-mode, 用文本文件管理日常(十四)

Org-mode, 用文本文件管理日常(十五)

Org-mode, 用文本文件管理日常(十六)

哪些我不再使用功能

下面列了些我知道的功能,但是我不怎么用它们。
org-mode 包含大量功能。有很多功能我还不清楚或者说还没有暴露出来,所以这个列表暂时还没有完成。

归档相同层级的子树

这个是个可笑的想法,但是我觉得归档整个子树更合适些。我不介意大量任务被标记DONE (还没有归档)。

处理字符格式

当从其它文件拷贝文本到org-mode文件中,字符中的格式会使得文档可读性很差。通过如下设置,可以移除字符中格式,这样看上去更好点。

(setq org-emphasis-alist (quote (("*" bold "" "")
("/" italic "" "")
("_" underline "" "")
("=" org-code "" "" verbatim)
("~" org-verbatim "" "" verbatim))))

下标以及上标

我目前不会写些需要下标以及上标的文档。我通过下面方法禁用 _ 以及 ^ .

(setq org-use-sub-superscripts nil)

Yasnippet

Yasnippet 很棒,但是我不怎么用了。我使用 abbrev-mode以及 skeletons 来替换yasnippet,并且他们默认在emacs中就可用的。

Yasnippet 包含很多代码语言片段。我在 org-mode 中使用少量的babel相关的代码片段。

我下载安装了非绑定的版本的yasnippet,这样我就可以编辑预定义的代码片段。我将yasnippet解压到我的 ~/.emacs.d/plugins 目录,重命名 yasnippet0.5.10 为 yasnippet然后添加进我的 =.emacs=中。

我是这么使用代码片段:

  • begin 来生成 #+begin 块
  • dot 生成 graphviz
  • uml 生成 PlantUML图
  • sh 生成 bash shell 脚本
  • elisp 生成emacs lisp 脚本
  • 当开会是记录会议备忘时用来转换参会者全名

下面是 begin 代码块定义:
org-mode Yasnippet: ~/.emacs.d/plugins/yasnippet/snippets/text-mode/org-mode/begin

#name : #+begin_...#+end_
# --
#+begin_$1 $2
$0
#+end_$1

我这样创建 #+begin_** 代码块

  • #+begin_example
  • #+begin_src
  • 其他。

输入 begin 然后按 TAB 然后 begin 文本就会替换成代码段。当输入src TAB emacs-lisp TAB 代码块就完成了。我将这个流程缩减成 elisp TAB因为我经常用。

输入 C-c SingleQuote(‘) 就会插入你需要的emacs-lisp 代码。当进入这个块中,你就可以在一种你知道的格式以及相应着色的emacs lisp 脚本,非常好。 C-c SingleQuote(‘) 退回到org-mode。它可以识别任何emacs编辑模式,所以你只需要输入合适的mode名称即可。

dot

#dot : #+begin_src dot ... #+end_src
# --
#+begin_src dot :file $1 :cmdline -Kdot -Tpng
$0
#+end_src

uml

#uml : #+begin_src plantuml ... #+end_src
# --
#+begin_src plantuml :file $1
$0
#+end_src

sh

#sh: #+begin_src sh ... #+end_src
# --
#+begin_src sh :results output
$0
#+end_src

elisp

#elisp : #+begin_src emacs-lisp ...#+end_src emacs-lisp
# --
#+begin_src emacs-lisp
$0
#+end_src

节省了非常多时间。

只在奇数行或者偶数行显示标题

这个我已经使用org-indent-mode替换了。

我使用函数 org-convert-to-odd-levels 以及 org-covert-to-oddeven-levels 函数转换成奇数行或者偶数行。我最终会设置为奇偶行,这样可以减少任务上的空格。我不觉得只显示奇数行很好,当然全部显示也不是特别好。

同时设置父任务STARTED状态

我曾经既使用 STARTED 又使用 NEXT 状态。大部分情况来说他俩一个意思,唯一区别就是STARTED 可以表示任务刚开始计时。然后我就会将该任务设置成 NEXT 状态。

下面代码用来将 STARTED 状态同时应用在父任务上,但是目前我不这么用了。当一个任务被标记成 STARTED 状态(不管是手工还是计时器),那么它的父任务如果在 TODO或者 NEXT 时候就会被标记成 STARTED 状态。只要对第一个 NEXT 子任务标记成 STARTED那么父任务也会一样被标记。这能帮我跟踪正在进行的任务。

下面是这个功能的实现:

;; Mark parent tasks as started
(defvar bh/mark-parent-tasks-started nil)
(defun bh/mark-parent-tasks-started ()
"Visit each parent task and change TODO states to STARTED"
(unless bh/mark-parent-tasks-started
(when (equal org-state "STARTED")
(let ((bh/mark-parent-tasks-started t))
(save-excursion
(while (org-up-heading-safe)
(when (member (nth 2 (org-heading-components)) (list "TODO" "NEXT"))
(org-todo "STARTED"))))))))
(add-hook 'org-after-todo-state-change-hook 'bh/mark-parent-tasks-started 'append)

自动计时任务

我曾经在开源软件BZFlag做贡献。当需要发布时候,我会对测试BZFlag客户端计时。

我有个按键绑定在我的窗口管理器中,当执行该快捷键,就会对测试任务计时,并运行BZFlag客户端,当完成,恢复之前的任务计时。

(defun bh/clock-in-bzflagt-task ()
(interactive)
(bh/clock-in-task-by-id "dcf55180-2a18-460e-8abb-a9f02f0893be"))

该函数通过shell脚本调用:

,#!/bin/sh
emacsclient -e '(bh/clock-in-bzflagt-task)'
~/git/bzflag/trunk/bzflag/src/bzflag/bzflag -directory ~/git/bzflag/trunk/bzflag/data $*
emacsclient -e '(bh/resume-clock)'

恢复计时函数将计时器应用到之前的计时任务上。

(defun bh/resume-clock ()
(interactive)
(if (marker-buffer org-clock-interrupted-task)
(org-with-point-at org-clock-interrupted-task
(org-clock-in))
(org-clock-out)))

如果没有任务通过 bh/resume-clock 停止计时。

按键q隐藏agenda视图缓冲区

通过 Sticky Agendas ,q键默认行为就是退出缓冲区,因此这个目前
暂时不需要了。

我修改了agenda中 q 按键,替换关闭agenda 缓冲区,取而代之将其放入buffer list最尾端。这样我就可以使用 q 来快速将agenda视图切换回来,或者通过 =f9 f9=重新生成agenda视图。

(add-hook 'org-agenda-mode-hook
(lambda ()
(define-key org-agenda-mode-map "q" 'bury-buffer))
'append)

任务优先级

我使用agenda来选择下个需要做的任务。通常我不会用优先级来处理,但是在工作中我会从我的经理那边获取有些优先级搞的任务。这种情况我会将这个任务标记成高优先级。这时会让agenda按照优先级来显示任务。

我使用A-E来标记任务优先级,没有明确标记的任务使用最低的E优先级。

#+beginsrc emacs-lisp
(setq org-enable-priority-commands t)
(setq org-default-priority ?E)
(setq org-lowest-priority ?E)
#+endsr

使用git来同步历史,备份以及同步数据

修改org文件中折叠区域非常危险。我的做法是将org文件纳入 git 管理。

我的设置会每个小时保存所有的org文件,并自动创建commit。这方便我
及时回退并查看我的org状态,在文档生命周期期间。我使用这种方法来恢复
我不小心删除的折叠区域的数据。

自动定时提交

我的emacs设置一小时前1分钟开始保存所有org缓冲区。代码包含在 .emacs 中

(run-at-time "00:59" 3600 'org-save-all-org-buffers)

cron 任务任务每小时通过 org-save-all-org-buffers 保存所有缓冲区。
我使用脚本来提交代码,因此我可以按需要提交代码,并当从一台机器移动到
另一台机器,方便拉取所有修改。

crontab 细节:

0 * * * * ~/bin/org-git-sync.sh >/dev/null

~/bin/org-git-sync.sh

下面是shell脚本来创建所有的 git 提交。该循环遍历多个仓库,提交所有修改的文件。我有如下几个org-mode仓库。

  • org
    所有org 项目文件以及todo列表
  • doc-norang.ca
    所有 http://doc.norang.ca/ 下修改
  • www.norang.ca
    网站 http://www.norang.ca/ 所有修改

下面脚本不会创建空commit – git 只会对有修改的才会创建commit.

,#!/bin/sh
# Add org file changes to the repository
REPOS="org doc.norang.ca www.norang.ca"
for REPO in $REPOS
do
echo "Repository: $REPO"
cd ~/git/$REPO
# Remove deleted files
git ls-files --deleted -z | xargs -0 git rm >/dev/null 2>&1
# Add new files
git add . >/dev/null 2>&1
git commit -m "$(date)"
done

我使用如下的 .gitignore 文件来在我的 git 仓库中排除自动生成的文件。
如果需要导入ditaa或者graphviz我将会将其加到我的repo总。默认情况下所有PNG图像都被忽略(默认情况下我认为他们都是由 ditaa生成)

core
core.*
*.html
*~
.#*
\#*\#
*.txt
*.tex
*.aux
*.dvi
*.log
*.out
*.ics
*.pdf
*.xml
*.org-source
*.png
*.toc

Git- 让编辑更加自信

我在我所有目录下都用git,这样我所有的更新都可以通过git来跟踪。

这意味着我可以很自信的修改文件。改文件,破坏文件对我来说也不是问题。
通常我也非常方便回退到之前版本,来查看修改了那些东西。当修改配置文件时候,也非常方便(例如 apache webserver,bind9 DNS配置等。)

这非常方便,修改文件可能让破坏文件,但是如果有 git 来跟踪修改,那么你会很方便快速找回之前版本。当然包更新也是可以用git来管理。我将每个修改都保存在我的 git 仓库中。

git仓库同步

我买了台 Eee PC 1000 HE作为我的主要工作电脑来替换我的6年的 Toshiba Tecra S1.我有个LAN服务器作为我所有项目的git仓库。现在唯一的问题是我需要保留5分钟来保证我所有的文档更新到最新,当我把它带出去(没有网络连接)。

为解决这个问题,我在上面建了个bare仓库。它包含我的org-mode仓库以及其他感兴趣的仓库。

当我需要出去,我会通过 git-sync 脚本将我的工作站的bare git仓库更新到我的Eee PC上。对于merge冲突的仓库我先手动解决冲突,然后重新运行
git-sync 直到没有报错。这可能会花一到两分钟。然后我就可以带着我的Eee PC离开。当我在路上,我就有所有的历史以及git仓库。

git-sync 脚本替换我之前的脚本,它里边包含所有用到的工具。它能够完成如下工作:

  • 对于当前系统上的每个仓库
    • 检查ref能不能移动
    • 如果本地仓库比较老执行快速merge
    • 如果已经是最新的,什么都不需要处理
    • 提交修改到远程仓库
    • 当本地和远程有分支时,报错
    • 从远程获取对象
    • 对每个分支跟踪远程分支

这会使得我电脑上35个仓库自动更新,用最少的干预。我需要人工处理的只剩我在Eee PC以及我的工作站修改冲突– 这样就可以merge了。

下面是我的 git-sync 脚本。

,#!/bin/sh
#
# Local bare repository name
syncrepo=norang
reporoot=~/git
# Display repository name only once
log_repo() {
[ "x$lastrepo" == "x$repo" ] || {
printf "\nREPO: ${repo}\n"
lastrepo="$repo"
}
}
# Log a message for a repository
log_msg() {
log_repo
printf " $1\n"
}
# fast-forward reference $1 to $syncrepo/$1
fast_forward_ref() {
log_msg "fast-forwarding ref $1"
current_ref=$(cat .git/HEAD)
if [ "x$current_ref" = "xref: refs/heads/$1" ]
then
# Check for dirty index
files=$(git diff-index --name-only HEAD --)
git merge refs/remotes/$syncrepo/$1
else
git branch -f $1 refs/remotes/$syncrepo/$1
fi
}
# Push reference $1 to $syncrepo
push_ref() {
log_msg "Pushing ref $1"
if ! git push --tags $syncrepo $1
then
exit 1
fi
}
# Check if a ref can be moved
# - fast-forwards if behind the sync repo and is fast-forwardable
# - Does nothing if ref is up to date
# - Pushes ref to $syncrepo if ref is ahead of syncrepo and fastforwardable
# - Fails if ref and $syncrop/ref have diverged
check_ref() {
revlist1=$(git rev-list refs/remotes/$syncrepo/$1..$1)
revlist2=$(git rev-list $1..refs/remotes/$syncrepo/$1)
if [ "x$revlist1" = "x" -a "x$revlist2" = "x" ]
then
# Ref $1 is up to date.
:
elif [ "x$revlist1" = "x" ]
then
# Ref $1 is behind $syncrepo/$1 and can be fast-forwarded.
fast_forward_ref $1 || exit 1
elif [ "x$revlist2" = "x" ]
then
# Ref $1 is ahead of $syncrepo/$1 and can be pushed.
push_ref $1 || exit 1
else
log_msg "Ref $1 and $syncrepo/$1 have diverged."
exit 1
fi
}
# Check all local refs with matching refs in the $syncrepo
check_refs () {
git for-each-ref refs/heads/* | while read sha1 commit ref
do
ref=${ref/refs\/heads\//}
git for-each-ref refs/remotes/$syncrepo/$ref | while read sha2 commit ref2
do
if [ "x$sha2" != "x" -a "x$sha2" != "x" ]
then
check_ref $ref || exit 1
fi
done
done
}
# For all repositories under $reporoot
# Check all refs matching $syncrepo and fast-forward, or push as necessary
# to synchronize the ref with $syncrepo
# Bail out if ref is not fastforwardable so user can fix and rerun
time {
retval=0
if find $reporoot -type d -name '*.git' | {
while read repo
do
repo=${repo/\/.git/}
cd ${repo}
upd=$(git remote update $syncrepo 2>&1 || retval=1)
[ "x$upd" = "xFetching $syncrepo" ] || {
log_repo
printf "$upd\n"
}
check_refs || retval=1
done
exit $retval
}
then
printf "\nAll done.\n"
else
printf "\nFix and redo.\n"
fi
}
exit $retval