十 29
另一项常用的操作是利用 Edit 菜单的剪切,复制和粘贴功能。这些都是其他软件常见的功能,但是TextMate在这些标准方法的基础之上增加了历史剪贴板。让我们做一个简单的例子来加深理解:
- 新建一个 TextMate 文档 (Ctrl+N)
- 输入下面的文字
Line Three.
Line One.
Line Two.
- 你可以通过历史剪贴板来重新安排它们的顺序。移动到文档的第一行(Apple+上),选择这一行(Shift+Apple+L),剪切它(Apple+X)。对最后一行进行一样的操作(Apple+下,Shift+Apple+L,Apple+X)。然后再如此操作最后一行(上,Shift+Apple+L,Apple+X)。
- 现在你可以依次粘贴它们。用Apple+V粘贴你历史剪贴板里面的最后一个记录。如果你想粘贴历史剪贴板里面的上一条记录的话需要按Shift+Apple+V,再次按这个组合键你可以粘贴出最后历史剪贴板里面的第一条记录。现在你可以按希望的顺序来排列这些行了。
如果排序顺序和你想的不一样,别害怕。按Ctrl+Option+Apple+V或者选择Edit -> Paste from Histor, TextMate会打开一个历史剪贴板列表。你可以用方向键来选择条目并按回车选择该条目。或者按ESC退出列表。
注意,粘贴出来的文本的缩进位置是由TextMate根据当前文档而决定的。虽然大多数情况下这个特性可以节省你不少时间,但是可能你偏偏不想这样。希望 Paste Without Re-indent。打开Edit菜单并按下Ctrl键你能看到这个命令。当然你也可以直接使用Ctrl+Apple+V来进行这个操作。如果这个特性让你感到讨厌,你可以在配置中禁用它。
十 27
生成式编程(Generative Programming)——即用代码来编写代码,是一个包含了若干种技术的一种强大的技术。这种技术可以以最简单的方式实现,例如编写一段用于自动完成编程中一些单调乏味的操作的shell脚本。举个例子,假设你想为每个用户生成一个示例项目的测试fixture:
brad_project:
id: 1
owner_id: 1
billing_status_id: 12
john_project:
id: 2
owner_id: 2
billing_status_id: 4
...
如果这种语言不支持可脚本化(可编程)的测试fixture,你恐怕不得不手工编写了。当数据持续增加时这将会变得混乱,而且当这些测试fixture和源数据层有着奇怪的依赖关系的时候手工编写几乎变得不可能。
朴素的生成式编程能够让你编写脚本来从源数据生成这些测试fixture。虽然还不够理想,但是比起全部手工编写已经算是个巨大的进步了。但其维护仍然让人头痛,因为你不得不将那个脚本放在编译过程中,而且需要保证源数据发生了变化时测试fixture被重新生成了。
令人欣慰的是在Ruby或Rails中,这种烦恼几乎是不需要的。几乎每个方面的Rails应用的配置都是可脚本化的,这主要归因于使用了内部的特定领域语言(DSL)。在一个内部的DSL中,你能够支配使用Ruby语言的所有特性,而不仅仅是库文件作者决定要提供给你的那些特定接口。
再返回去看前面的例子,ERb将这个过程变得异常简单。我们可以通过使用ERb的<% %>和<%= %>标签在上面用到的YAML文件中插入任意的Ruby代码,包括我们需要的任何逻辑:
< % User.find_all_by_active(true).each_with_index do |user, i| %>
< %= user.login %>_project:
id: < %= i %>
owner_id: < %= user.id %>
billing_status_id: < %= user.billing_status.id %>
< % end %>
ActiveReacord中对这个功能的实现几乎不能再简单了:
yaml = YAML::load(erb_render(yaml_string))
使用了helper方法erb_render:
def erb_render(fixture_content)
ERB.new(fixture_content).result
end
十 27
我们常常想创建一个能够随着运行时的数据变换其方法的接口。Rails中这方面最突出的一个例子就是ActiveRecord的属性存取方法。ActiveRecord对象的方法调用(例如person.name)在运行时将被转换成属性存取。在类方法级别,ActiveRecord提供了极大的灵活性:Person.find_all_by_user_id_and_active(42,true)被转换成相应的SQL查询,如果被查询的属性不存在那么就会抛出一个标准的NoMethodError错误。
其背后的秘密是Ruby的method_missing方法。当对象上的一个不存在的方法被调用的时候,Ruby在抛出NoMethodError前首先检查此对象的类是否存在method_missing方法。method_missing的的第一个参数是被调用方法的名称;剩下的那些参数对应那些传递到被调用方法的参数。任何传递到方法的块也会被传递到method_missing方法。因此,一个完整方法的轮廓如下:
def method_missing(method_id, *args, &block)
...
end
使用method_missing有下面的几个缺点:
- 在方法查找时它要比普通的方法慢。简单的测试表明用method_missing来调用方法的时间要比用常规方式进行调用多耗费两到三倍的时间。
- 因为被调用的那些方法根本不实际存在,所以他们只有在方法查找整个过程的最后一步才能被截取到,也就是说它们不会像常规的方法那样被生成文档或者内省。
- 因为所有的动态方法都必须通过method_missing方法,所以当一个方法含有各种各样代码,而这些代码又需要动态添加方法时其方法体将变得异常庞大。
- 使用method_missing将限制其与API后续版本的兼容性。一旦依赖method_missing生成的那些未定义的方法来完成特定的操作后,在API的后续版本中引入的那些新方法也许会使你的用户失望。
ActiveRecord中提供的generate_read_methods 特性是一个弥补这些缺失很好的替代方案。ActiveRecord 不是等待method_missing来截取方法调用而是自己实现了属性的设置和读取方法,这样它们就可以通过常规方法的分派来被调用了。
总的来说这是个强大的方法,Ruby的动态本性使得那些方法在其被第一次调用时,用其更优化的版本来替代其本身成为可能。它也被用于要求迅速响应的Rails路由;我们将在本章的后续部分看到它的影子。
十 27
在代码块或者过程访问定义在其作用范围以外的变量时,就会创建闭包(Closures)。即使包含原始变量的代码块已经超出作用范围了,但那些被其它块和过程引用的变量也会被一直被保留着,直到引用它们的块和过程全部超出作用范围。下面的这个极度简化的例子,虽然确实是没有什么实际的用途,但能够很好的解释上面的情景:
def get_closure
data = [1, 2, 3]
lambda { data }
end
block = get_closure
block.call # => [1, 2, 3]
从get_closure中的匿名方法(即lambda)返回的是定义在其作用范围外的局部变量data。只要块变量一直在作用范围内,它将保存其对data的引用,而且这个data的实例也不会被消除(即使get_closure方法已经返回)。需要注意的是每次get_closure被调用时,data将指向不同的变量(因为它是方法局部变量):
block = get_closure
block2 = get_closure
block.call.object_id # => 76200
block2.call.object_id # => 76170
闭包的一个典型的例子就是make_closure方法,它返回一个累加方法,当被执行时进行累加并返回累加的当前值。在Ruby中,make_counter 实现代码如下:
def make_counter(i=0)
lambda { i += 1 }
end
x = make_counter
x.call # => 1
x.call # => 2
y = make_counter
y.call # => 1
y.call # => 2
lambda方法创建了一个闭包,它覆盖了局部变量i的当前值。该变量不仅可以被访问,其值亦可以被修改。每个闭包获得的是各自不同的变量实例(因为实例化make_counter的时候它们都是局部变量),因此x与y引用了不同的局部变量i的实例,这些实例拥有自己独立的状态。
十 27
当前的Ruby实现有一点不足:Blocks代码块不总是Procs过程,Procs过程也不总是Blocks代码块。普通的代码块(定义在do…end 或者{}中)必须依附在一个方法调用上,而不能自动成为对象。例如,你不能把代码写成code_block = {puts “abc”}。这就是Kernel#lambda 和Proce.new 的用途:将代码块转换为过程。
block_1 = lambda { puts "abc" } # => #<Proc:0x00024914@-:20>
block_2 = Proc.new { puts "abc" } # => #<Proc:0x000246a8@-:21>
Kernal#lambda与Proc.new之间仅存在着些许区别。由Kernel#lambda 创建的Proc被调用时返回给调用方法的值是在Proc中给定的值;而由Proc.new 创建的Proc被调用的时候,Proc将试图从当前的函数方法中返回,但是,如果不能从方法调用中返回那么它就抛出LocalJumpError错误。下面是一个具体的例子:
def block_test
lambda_proc = lambda { return 3 }
proc_new_proc = Proc.new { return 4 }
lambda_proc.call # => 3
proc_new_proc.call # =>
puts "Never reached"
end
block_test # => 4
lambda_proc 中的返回语句从lambda中返回3。相反地,proc_new中的返回语句是从函数方法返回,因此——block_test,其返回的值是4。那个puts 语句将永远不能被执行到,因为proc_new_proc.call 语句首先从block_test返回。
通过将块传给一个方法,也能将其转换为过程,与常规的参数传递有所不同的是需要在其前面加上一个&符:
def some_function(&b)
puts "Block is a #{b} and returns #{b.call}"
end
some_function { 6 + 3 }
# >> Block is a #<Proc:0x00025774@-:7> and returns 9
相反的,可以将前面加了&符的过程传递给一个需要块作参数的方法:
add_3 = lambda {|x| x+3}
(1..5).map(&add_3) # => [4, 5, 6, 7, 8]
十 27
你可能常常需要把文字移动到不同的地方,Text -> Move Selection为你提供如下功能:
- Ctrl+Apple+上 向上移动一行(如果没有选择任何文本则移动当前行)
- Ctrl+Apple+下 向下移动一行(如果没有选择任何文本则移动当前行)
- Ctrl+Apple+左 移动当前所选文字向左一个字符
- Ctrl+Apple+右 移动当前所选文字向右一个字符
TextMate的 Text 菜单也可以让读者很容易的进行文本缩进:
- Apple+[ 或者 Option + Tab 减少缩进(如果没有选择任何文本则减少当前行的缩进)
- Apple+] 或者 Option + Shift + Tab 增加缩进(如果没有选择任何文本则增加当前行的缩进)
- Option+Apple+[ 根据目前编辑文档的语言类型自动格式化缩进
十 27
TextMate有两个功能在你输入文档的时候会给你提供很大的帮助。当然它们也很常见,单词补完和符号自动配对。
我只需要教你一个键,就可以让单词补完帮你节省大量时间。每当你要更改文档内容的时候,输入几个开头字母,然后按ESC键。TextMate可以自动用其开头的变量,方法,标签或者其他文档中已经存在的单词来自动补完。此外,语法检查器为每种语言都提供了一套默认的自动补完。这对程序员来说是一个非常重要的功能,它可以避免很多打字错误。
如果有多个相关单词供你选择,你只需要多次按ESC就可以在它们之间移动选择。TextMate会根据你本次的选择自动为你排序出现相关词语的顺序,以便为你今后的操作节省更多时间。如果不小心多按了一下ESC,那么可以按Shift+ESC选择前一个。
例如你在编写一些服务端的程序。首先你要敲入一个很长的变量名字 idle_connections 。以后你可是按 idl 然后按ESC来迅速补完这个变量名。根据你文档中以这3个字母开头单词的数量,有时候你可能得多按1,2次ESC键。不过别忘了,TextMate会记录你选择每个单词的次数来帮你排序。这会确保你不会因为手得瑟一下,输入错误的变量名。不单单是编程相关的这些变量,任何你文档中出现的又长又难拼写的单词你都可以这样补完。
你可以在Edit -> Completion下找到这个自动补完的命令,这或许是你最重要的键盘快捷键。相信我,除了担心它可能磨损你的ESC键之外,你真的没理由不常常使用它!
如果你已经开始使用TextMate有段时间了,你可能早就不自觉的用上了符号自动配对功能。大多数情况下,你输入一个左括号(,TextMate会自动给你补上一个右括号)。并且把你的光标定位在括号之间。当你输入完其中的内容后,你手动输入一个右括号),光标会自动跳出到括号之外。这个功能在大部分符号自动配对功能中都可以使用,包括引号。每种语言都有一些自己特殊的符号配对,例如在CSS中,你输入一个冒号: 会自动给你在其后增加一个分号;
符号自动配对功能的另一个优点它结合文字选择区域。当你选取了一部分文字之后,如果你输入一个可以配对的符号,那么符号的开始符会自动插入到文字之前,而符号的结尾符会插入到文字后面。如下图,相信你会喜欢上这个功能。

十 25
在传统的选择器基础上,TextMate允许你使用直角选择器或者列选择器。在你拖动鼠标进行选择的时候,可以按住Option键,这样就启用了直角选择器。也可以使用 Edit -> Change to Column Selection或者Option切换是否使用列选择器。
下面的例子告诉你如何快速的产生一个名字列表:
- 创建一个新的TextMate文档 File -> New 快捷键Apple+N
- 输入一些 first names,每一个单独占一行。 Jim,Marsha, James, Dana, Andrew。
- Apple+上 移动光标到文档的开头
- Shift+Option+下 选取到列结尾的全部文档。这个快捷键看起来好像没有选取整个列文档,但是由于你的倒数第二行有一个换行符。事实上列选择器已经扩展到最后一行的开头了。下一步会让这一切更清晰。
- 按Option打开列选择器,仔细看你所有选取的列前面都有一列细线,大概1像素,这回知道了吧上一步已经选取了整个列。
- 输入 last name 例如 Gray,随后跟一个逗号和空格。你会发现刚才选取的所有行同时都增加了。
移动光标到新的一行,离开列选择器模式。
令人惊讶的,当使用软包围行的时候列选择器会起冲突。和其它的编辑器不同,TextMate支持打开列选择器View -> Soft Wrap (Option+Apple+W)。然而,列选择器不会把包含的内容当作新行。勾选上View -> Gutter -> Soft Wrap Indicators 之后你可以通过编辑器窗口左边的原点看到哪一行被包含了。 (这一段翻译的不好,我自己也迷糊回头好好研究一下这个用法会修改)。
十 25
编辑文本的第一部是选择它,选择一些文本之后你可以通过命令移动它,在搜索其中的代码或者干脆过滤删除掉它。这也是为什么TextMate尽可能简单的让你选择任意数量的文本的原因。
首先俺要告诉你一个好消息,前几章里面讲的移动光标的快捷键在选择文本这里一样可以用哦。注意上文介绍的来自于Emacs的那些快捷键不能跟Shift共用。按住Shift键然后再利用移动光标的快捷键。根据光标位置的不同你就可以选取,扩大所选或者撤销所选的文本。下面举个例子
- 按 Apple+右 把光标移动到行的末尾。
- 按 Shift+Apple+左 选取从行末尾至行开头的全部文本。
如果对你来说这一操作需要2步还太麻烦的话。你可以用自定义快捷键或者宏命令来简化。如果可能我会继续翻译那部分。不过那是很遥远很遥远的事啦。还好TextMate内置了这样一条命令,Shift+Apple+L可以选取当前行的文本。另外我个人介绍一个快捷键,Ctrl+W可以选择光标所在的单词。
TextMate有一个唯一的内置选择器Current Scop,快捷键是Ctrl+Option+B。它根据当前文档的语法检查来改变选择范围。你可以使用这个快捷键来选择编程语言特有的元素。例如你可以像下图一样(说实话我很少用这个快捷键)。

你可以在Edit -> Select下面找到全部内置的选择器。花点时间在这里,下次当你选择文本的时候你就知道你该用哪个快捷键了。
十 25
当你移动光标的时候,折叠代码片段不是必须的技术。但是如果你要在文档中跳过大段不需要的代码,或许他还是有用滴。
当你用TextMate编辑一个它能理解的结构文档的时候,编辑窗口的左侧应该有一个折叠标记栏。使用View -> Gutter -> Foldings确认它已经显示出来。
你可以单击折叠符号来选择展开或者折叠代码。也可以使用F1键来进行这一操作,F1会折叠目前光标所在一行的最接近的可折叠代码。如果你进行了这一操作,TextMate会把该代码片段折叠为一行,后面用省略号结尾。而且编辑窗口左侧的折叠符号也会相应的变化。
如果你需要瞄一眼折叠了哪些代码,只需要把鼠标移动到省略号的位置就可以看到了。如下图。点一下省略号,或者把光标移动到折叠代码的这一行并且按F1,就可以展开代码。

你可以使用View -> Toggle Foldings at Level或者快捷键来完成大部分的折叠和展开功能。根据文档类型,通常建议你用Option+Apple+2或者Option+Apple+3来折叠代码,尤其当你编辑的是Rails Controller的时候,这个快捷键尤其好用。根据文档嵌套层次的不同,你也可以使用其他数字键来配合使用。