十 31
如下图所示,你可以在编辑窗口的下面修改制表符的大小或者使用Soft Tabs模式。如果你选择了Soft Tabs类型,那么TextMate会移除目前的制表符,当你保存文档的时候,它会用空格来替代。并且今后编辑同样语言类型文档,也会如此。

在TextMate里面你大概还会遇到Smart Tab的情况,当你按一个Tab键的时候,会根据目前的缩进级别自动在前面填充相应数量的制表符。当你选取一部分文字的时候,可以使用 Text -> Convert -> Spaces to
Tabs 或者 Text -> Convert -> Tabs to Spaces功能,如果你没有选择任何文本,则该操作会影响当前文档。
十 31
你会看到TextMate支持常见得对齐方式,它们在 Text 菜单得子菜单下。Text菜单包含3个有用得方法
- Ctrl+Q 左对齐段落
- Ctrl+J 使段落两边对齐
- Ctrl+Option+Q 把段落合并为一行
当使用列选择器的时候,这些快捷键可以很方便的调整你的内容。这一过程是很容易的,选择文本内容中的一部分按Ctrl+Q。如下图

十 30
TextMate支持两种额外的编辑模式,当你输入固定宽度的文本或者ASCII的时候尤其有用。它们是Freehanded Editing和 Overwrite,你可以在 Edit -> Mode 下找到它们。
当你切换 Freehanded Editing 模式(Option+Apple+E),TextMate允许你使用键盘来移动光标或者用鼠标点击。你可以放到一行的结尾外,或者列的中间。如果你增加新内容,TextMate会在特定区域补充上空白。
在使用列选择器的时候,这个功能通常是激活的,所以你可以把内容区域扩充。如果你希望单击鼠标切换这个模式,那么当你单击的时候请按住Option键。
另一个编辑模式Overwrite Mode (Option+Apple+O),用来改变目前存在的文本。你很容易就能辨识出现在现在是否激活了这个模式,因为它会让你的光标成为一个下划线。和在两个字符中间插入新字符不同,TextMate的这种编辑模式会用你输入的字符替换当前字符。当你要进行文本替换的时候,这个模式可以很好的避免行内后面的文本窜位。
这些模式会引起自动化操作的异常,如果你看到了一些奇怪的结果请确保已经关闭了这些模式。如果有必要,请每次需要使用这些模式的时候手动开启。
十 29
TextMate允许你一次编辑多行文本。你可以使用这个功能添加新内容或者删除旧内容。这个功能在你操作列表数据的时候尤其方便。让我们仍然用一个例子来熟悉这个用法:
- 创建一个新的TextMate文档,Ctrl+N
- 选择当前工作的语言环境 Ctrl+Option+Shift+H
- 增加一些基础的文档结构,doctype+Tab键然后选择2.增加必须的根节点html然后按Ctrl+< 然后按回车会给你留一些空间输入内容。
- 现在输入header区域,head+Tab键。在title中输入Apple Products。按两次方向键下,按回车给body留出空间。
- 输入body然后按Ctrl+< 然后按回车键。
- 离开head区域,现在创建一个标签元素。按Ctrl+< 然后输入h1 再按Tab键跳到内容区输入 Computers TextMate Runs On: 然后按Apple+回车两次。跳过关闭标签。并留出一些空白区域。
- 同样的办法,创建ul标签。输入下面内容
Mac mini
iMac
Mac Pro
Macbook
Macbook Pro
你需要选择这些内容,然后才能一次给它们添加上标签。首先把你的光标移动到最后Macbook Pro的右边,然后按Shift+Option+左两次来选择这一行,再按Shift+Option+上最后按Shift+Option+左把空格也选上。现在使用最牛B的Ctrl+Shift+Apple+W组合键。
- 使用Ctrl+Option+Apple+P来预览
接下来我要教你如何给刚刚的列表增加点东西以便识别哪些是台式机哪些是笔记本:
- 按Ctrl+Shift+L选择第一行。按住Shift再按两次方向键下。
- 编辑选择行快捷键是Option+Apple+A
- 按Option+左 两次输入( desktop )
- 同样给后两行增加 ( laptop )
如同你看到的 Edit Each Line in Selection 允许你一次给多行添加或者删除内容。它和列选择器很像,它通常用来给一堆列内容增加新内容。
十 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+[ 根据目前编辑文档的语言类型自动格式化缩进