在OSX上修改用homebrew安装的mysql的bind-ip

使用homebrew安装的mysql默认bind-ip是127.0.0.1 homebrew安装的mysql是launchctl管理的 首先找到mysql.plist这个文件

1
locate mysql.plist

我的mysql.plist在/usr/local/Cellar/mysql/5.6.22/homebrew.mxcl.mysql.plist 编辑mysql.plist修改bind-address=0.0.0.0

1
2
launchctl unload /usr/local/Cellar/mysql/5.6.22/homebrew.mxcl.mysql.plist
launchctl load /usr/local/Cellar/mysql/5.6.22/homebrew.mxcl.mysql.plist

之后运行

1
launchctl list|grep mysql

查看launchctl中mysql的pid

1
56047    1   homebrew.mxcl.mysql

56047就是pid 如果56047为-的话运行

1
launchctl start homebrew.mxcl.mysql

Java Cucumber使用总结

最近这几个月一直在做java, java在cucumber方面可能不如ROR方便, 也缺少一些有用的工具支持,比如:database_cleaner, factory_girl等, 下面分享一下java在cucumber的一些使用经验

  • 以maven项目为例,首先在pom.xml中加入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  <dependency>
      <groupId>info.cukes</groupId>
      <artifactId>cucumber-java</artifactId>
      <version>1.2.0</version>
      <scope>test</scope>
  </dependency>
  <dependency>
      <groupId>info.cukes</groupId>
      <artifactId>cucumber-junit</artifactId>
      <version>1.2.0</version>
      <scope>test</scope>
  </dependency>
  <dependency>
      <groupId>info.cukes</groupId>
      <artifactId>cucumber-spring</artifactId>
      <version>1.2.0</version>
      <scope>test</scope>
  </dependency>
  • 创建cucumber的入口文件,在src/test/java文件夹下创建 com.cbss.cucumber.steps.CbssIT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RunWith(Cucumber.class)
@CucumberOptions(plugin = { "pretty", "html:target/cucumber" }, features = { "src/test/resources/features/" })
public class CbssIT {

    private static EmbeddedTomcat tomcat = new EmbeddedTomcat();

    @BeforeClass
    public static void setup() {
        if (!tomcat.isRunning()) {
            tomcat.start();
            tomcat.deploy("cbss_server");
            LogFactory.getInstance().getServerLogger().debug("tomcat-start ...");
        }
    }

    @AfterClass
    public static void teardown() {
        if (tomcat.isRunning()) {
            tomcat.stop();
            LogFactory.getInstance().getServerLogger().debug("tomcat-stop ...");
        }
    }

}

EmbeddedTomcat是内嵌的tomcat在cucumber开始前部署项目,下面说一下EmbeddedTomcat的具体实现,在pom.xml中引入包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-catalina</artifactId>
      <version>8.0.21</version>
  </dependency>
  <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-util</artifactId>
      <version>8.0.21</version>
  </dependency>
  <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
      <version>8.0.21</version>
  </dependency>
  <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <version>7.0.8</version>
  </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class EmbeddedTomcat {
  private Tomcat tomcat;

  public void start() {
    try {
      tomcat = new Tomcat();
      // If I don't want to copy files around then the base directory must be '.'
      String baseDir = ".";
      tomcat.setPort(8080);
      tomcat.setBaseDir(baseDir);
      tomcat.getHost().setAppBase(baseDir);
      tomcat.getHost().setDeployOnStartup(true);
      tomcat.getHost().setAutoDeploy(true);
      tomcat.start();
    } catch (LifecycleException e) {
      throw new RuntimeException(e);
    }
  }

  public void stop() {
    try {
      tomcat.stop();
      tomcat.destroy();
      // Tomcat creates a work folder where the temporary files are stored
      FileUtils.deleteDirectory(new File("work"));
      FileUtils.deleteDirectory(new File("tomcat.8080"));
    } catch (LifecycleException e) {
      throw new RuntimeException(e);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public void deploy(String appName) {
    tomcat.addWebapp(tomcat.getHost(), "/" + appName, "src/main/webapp");
  }

  public String getApplicationUrl(String appName) {
    return String.format("http://%s:%d/%s", tomcat.getHost().getName(),
        tomcat.getConnector().getLocalPort(), appName);
  }

  public boolean isRunning() {
      return tomcat != null;
  }
}
  • 在src/test/resources/features/ 下创建一个test.feature文件
1
2
3
4
5
6
7
8
9
10
11
12
# language: zh-CN

功能: 附属码下发业务逻辑测试

  背景:
      假如已经存基本的信息
      
  场景: 用户没有填写手机号码,不应该注册成功
      假如存在一个用户
      而且该用户没有填写电话号码
      当用户注册
      那么不应该注册成功
  • 在eclipse中使用junit运行CbssIT或者在命令行中运行 mvn test -Dtest=com.cbss.cucumber.steps.CbssIT后,会显示未完成的steps,创建一个java文件实现这些steps
1
2
3
4
5
6
7
  @ContextConfiguration("/cucumber.xml")
  public class XxxSteps
      @假如("^存在一个用户$")
    public void 存在一个用户 throws Throwable {
        ...
    }
  end

cucumber.xml为

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">


    <import resource="classpath:test-applicationContext.xml"/>

</beans>

在test-applicationContext.xml中可以配置测试的数据库和redis等等 自此cucumber的环境已经基本搭建完成了,下面说一下 database_cleaner, factory_girl 等工具的实现

  • database_cleaner: 创建DataBaseCleaner类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DataBaseCleaner {

    @Autowired
    DataSource dataSourceMain;
    @Autowired
    RedisFactory redisFactory;

    public void clean() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceMain);
        String[] tables = { "table1", "table2", "table3"};
        jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS=0");
        for (int i = 0; i < tables.length; i++) {
            jdbcTemplate.execute("truncate table " + tables[i]);
        }
        redisFactory.flushDB();
    }

在场景结束后会执行DataBaseCleaner的clean方法

1
2
3
4
     @After
    public void afterScenario() throws IOException {
        dataBaseCleaner.clean();
    }
  • factory_girl: 创建package:com.cbss.cucumber.factory, 在com.cbss.cucumber.factory包下面创建Builder.java和FactoryGirl.java和UserBuilder.java
1
2
3
4
5
public interface Builder<T> {

    T build();

}
1
2
3
4
5
6
7
8
9
@Component
public class FactoryGirl {

    public Builder<?> create(String name) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
        String packageName = FactoryGirl.class.getPackage().getName();
        return (Builder<?>) Class.forName(packageName + "." + name + "Builder").newInstance();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class UserBuilder implements Builder<User>{

    private static int sequenceId = 20094;

    private static int sequenceUserName = 0;

    public int getSequenceId() {
        sequenceId += 1;
        return sequenceId;
    }

    public int getSequenceUserName(){
        sequenceUserName += 1;
        return sequenceUserName;
    }

    @Override
    public User build() {
        User user = new User();
        user.setId(getSequenceId());
        user.setName("user" + getSequenceUserName());
        user.setXXX("xxx");
        ...
        ...
        return user;
    }

}

创建user的时候可以调用

1
User user = (User)factoryGirl.create("User");

避免了像以下这样的重复代码

1
2
3
4
5
6
User user = new User()
user.setId(id);
user.setName(name)
user.setXxx("xxx");
...
...

Git工作流程(转)

  如果你不理解Git的设计动机,那你就会处处碰壁。知道足够多的命令和参数后,你就会强行让Git按你想的来工作,而不是按Git自己的方式来。 这就像把螺丝刀当锤子用,也能把活干完,但肯定干的差极了,花费很长时间,还会弄坏螺丝刀。

  想想常见的Git工作流程是怎么失效的吧。

  多数时候这样做的效果会如你所愿,因为从你创建分支到合并回去之间,Master一般都会有些变动。然后,有一天当你想把一个功能(feature)分支合并进Master的时候,而Master并没有像以往那样有变动,问题来了:这时Git不会进行合并commit,而是将Master指向功能分支上的最新commit。 看图.不幸的是,你的功能分支有用来备份代码的commit(作者称之为checkpoint commit),这些经常进行的commit对应的代码可能处于不稳定状态!而这些commit现在没法和Master上那些稳定的commit区分开来了。当你想回滚的时候,很容易发生灾难性后果。

  于是你就记住了:“当合并功能分支的时候,加上 -no-ff 选项强制进行一次全新的commit。”嗯,这么做好像解决问题了,那么继续。

  然后一天你在线上环境中发现了一个严重bug,这时你需要追溯下这个bug是什么时候引入的。你运行了bisect命令,但却总是追溯到一些不稳定的commit。因此你不得不放弃,改用人肉检查。

  最后你将bug范围缩小到一个文件。你运行blame命令查看这个文件在过去48小时里的变动。然后blame告诉你这个文件已经好几周没有被修改过了 —— 你知道根本不可能没有变动。哦,原来是因为blame计算变动是从第一次commit算起,而不是merge的时候。你在几周前的一次commit中改动了这个文件,但这个变动今天才被merge回来。

  用no-ff来救急,bisect又临时失效,blame的运作机制又那么模糊,所有这些现象都说明一件事儿,那就是你正在把螺丝刀当锤子用。

反思版本控制

版本控制的存在是因为两个原因。

  首先,版本控制是用来辅助写代码的。因为你要和同事同步代码,并经常备份自己的代码。当然了,把文件压缩后发邮件也行,不过工程大了大概就不好办了。

  其次,就是辅助配置管理工作。其中就包括并行开发的管理,比如一边给线上版本修复bug,一边开发下一个版本。配置管理也可以帮助弄清楚变动发生的具体时间,在追溯bug中是一个很好的工具。

  一般说来,这两个原因是冲突的。

  在开发一个功能的时候,你应该经常做备份性的commit。然而,这些commit经常会让软件没法编译。

  理想情况是,你的版本更新历史中的每一次变化都是明确且稳定的,不会有备份性commit带来的噪声,也不会有超过一万行代码变动的超大commit。一个清晰的版本历史让回滚和选择性merge都变得相当容易,而且也方便以后的检查和分析。然而,要维护这样一个干净的历史版本库,也许意味着总是要等到代码完善之后才能提交变动。

  那么,经常性的commit和干净的历史,你选择哪一个?

  如果你是在刚起步的创业公司中,干净的历史没有太大帮助。你可以放心地把所有东西都往Master中提交,感觉不错的时候随时发布。

  如果团队规模变大或是用户规模扩大了,你就需要些工具和技巧来做约束,包括自动化测试,代码检查,以及干净的版本历史。

  功能分支貌似是一个不错的折中选择,能够解决基本的并行开发问题。当你写代码的时候,可以不用怎么在意集成的问题,但它总有烦到你的时候。

  当你的项目规模足够大的时候,简单的 branch/commit/merge 工作流程就出问题了。缝缝补补已经不行了。这时你需要一个干净的版本历史库。

  Git之所以是革命性的,就是因为它能同时给你这两方面的好处。你可以在原型开发过程中经常备份变动,而搞定后只需要交付一个干净的版本历史。

工作流程

考虑两种分支:公共的和私有的。

  公共分支是项目的权威性历史库。在公共分支中,每一个commit都应该确保简洁、原子性,并且有完善的提交信息。此分支应该尽可能线性,且不能更改。公共分支包括Master和发行版的分支。

  私有分支是供你自己使用的,就像解决问题时的草稿纸。

  安全起见,把私有分支只保存在本地。如果你确实需要push到服务器的话(比如要同步你在家和办公室的电脑),最好告诉同事这是私有的,不要基于这个分支展开工作。

  绝不要直接用merge命令把私有分支合并到公共分支中。要先用reset、rebase、squash merges、commit amending等工具把你的分支清理一下。

  把你自己看做一个作者,每一次的commit视为书中的一章。作者不会出版最初的草稿,就像Michael Crichton说的,“伟大的书都不是写出来——而是改出来的”(“Great books aren’t written – they’re rewritten.”)。

  如果你没接触过Git,那么修改历史对你来说好像是种禁忌。你习惯于认为提交过的所有东西都应该像刻在石头上一样不能抹去。但如果按这种逻辑,我们在文本处理软件器中也不应该使用“撤销”功能了。

  实用主义者们直到变化变为噪音的时候才关注变化。对于配置管理来说,我们关注宏观的变化。日常commit(checkpoint commits)只是备份于云端的用于“撤销”的缓冲。

  如果你保持公共历史版本库的简洁,那么所谓的fast-forward merge就不仅安全而且可取了,它能保证版本变更历史的线性和易于追溯。

  关于 -no-ff 仅剩的争论就只剩“文档证明”了。人们可能会先merge再commit,以此代表最新的线上部署版本。不过,这是反模式的。用tag吧。

规则和例子

根据改变的多少、持续工作时间的长短,以及分支分叉了多远,我使用三种基本的方法

1)短期工作

  绝大多数时间,我做清理时只用squash merge命令。   假设我创建了一个功能分支,并且在接下来一个小时里进行了一系列的checkpoint commit。

git checkout -b private_feature_branch
touch file1.txt
git add file1.txt
git commit -am "WIP"

完成开发后,我不是直接执行git merge命令,而是这样:

git checkout master
git merge --squash private_feature_branch
git commit -v

然后我会花一分钟时间写个详细的commit日志。

2)较大的工作

  有时候一个功能可以延续好几天,伴有大量的小的commit。

  我认为这些改变应该被分解为一些更小粒度的变更,所以squash作为工具来说就有点儿太糙了。(根据经验我一般会问,“这样能让阅读代码更容易吗?”)

  如果我的checkpoint commits之后有合理的更新,我可以使用rebase的交互模式。

  交互模式很强大。你可以用它来编辑、分解、重新排序、合并以前的commit。

  在我的功能分支上:

git rebase --interactive master 

  然后会打开一个编辑器,里边是commit列表。每一行上依次是:要执行的操作、commit的SHA1值、当前commit的注释。并且提供了包含所有可用命令列表的图例。

  默认情况下,每个commit的操作都是“pick”,即不会修改commit。

pick ccd6e62 Work on back button
pick 1c83feb Bug fixes
pick f9d0c33 Start work on toolbar 

  我把第二行修改为“squash”,这样第二个commit就会合并到第一个上去。

pick ccd6e62 Work on back button
squash 1c83feb Bug fixes
pick f9d0c33 Start work on toolbar 

  保存并退出,会弹出一个新的编辑器窗口,让我为本次合并commit做注释。就这样。

  舍弃分支

  也许我的功能分支已经存在了很久很久,我不得不将好几个分支合并进这个功能分支中,以便当我写代码时这个分支是足够新的的。版本历史让人费解。最简单的办法是创建一个新的分支。

git checkout master
git checkout -b cleaned_up_branch
git merge --squash private_feature_branch
git reset 

  现在,我就有了一个包含我所有修改且不含之前分支历史的工作目录。这样我就可以手动添加和commit我的变更了。

总结

  如果你在与Git的默认设置背道而驰,先问问为什么。将公共分支历史看做不可变的、原子性的、容易追溯的。将私有分支历史看做一次性的、可编辑的。推荐的工作流程是: 基于公共分支创建一个私有分支。 经常向这个私有分支commit代码。一旦你的代码完善了,就清理掉私有分支的历史。 将干净的私有分支merge到公共分支中。

Ruby中的||=操作符

1
2
3
def current_user
  @current_user ||= session[:user_id] && User.find(session[:user_id])
end

短短一行代码,却含有很多逻辑,以前老是搞混,这里总结一下。

这句代码相当于

1
2
3
4
5
6
7
8
9
10
11
12
def current_user
    if @current_user
       return @current_user
    else
       if session[:user_id]
          @current_user = User.find(session[:user_id])
       else
          @current_user = nil
       end
       return @current_user
    end
end

展开后的代码看起来很恶心,代码意思为:如果@current_user不为空直接返回@current_user。 如果@current_user为空,则根据session中的user_id判断是否登录,如果已经登录则查找出用户信息并返回。如果没有登录则返回空。

这里总结下各符号用法: and 与 && 为和,区别是and优先级比&&低。 or 与 || 为或,not与!为非,区别均是前者优先级低于后者 &&=, !=, ||=这个比较灵活,以前习惯用Java,可以认为它相当于Java里的+=,-=。 a &&= b即为a = a && b。可见Ruby比Java灵活很多。

Ruby的&&, ||与其它语言有些不同。 &&运算法则为:左边为false或nil时,直接分别返回false或nil,右边将不会运算。 左边不为false或nil时,返回右边的对象。 ||运算法则为:左边为false或nil时,返回右边的对象。 左边不为false或nil时,直接返回左边的对象,右边的不会运算。 我整理了几个例子:

1
2
3
4
5
6
7
8
9
10
11
puts false && "abc"      # => false
puts nil   && "abc"      # => nil

puts true  && "abc"      # => "abc"
puts "123" && "abc"      # => "abc"

puts false || "abc"      # => "abc"
puts nil   || "abc"      # => "abc"

puts true  || "abc"      # => true
puts "123" || "abc"      # => "123"

如果你想深入请看下面,不深入就算了 其实||=这个运算符里面比较复杂,或者说有点乱。 x ||= y 相当于 x || x=y 而不是 x = x||y 区别在于如果x存在且不为空时不会执行任何操作,直接返回。 还相当于

1
2
3
4
5
if defined? x
    x || x=y
else
    x = y
end

关于bash_profile和bashrc

/etc/profile:

此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行. 
并从/etc/profile.d目录的配置文件中搜集shell的设置.

/etc/bashrc:

为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取. ~/.
bash_profile:每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,
该 文件仅仅执行一次!默认情况下,他设置一些环境变量,
执行用户的.bashrc文件. ~/.bashrc:该文件包含专用于你的bash shell的bash信息,
当登录时以及每次打开新的shell时,该 该文件被读取. ~/.
bash_logout:当每次退出系统(退出bash shell)时,执行该文件.  

另外,/etc/profile中设定的变量(全局)的可以作用于任何用户,而~/.bashrc等中设定的变量(局部)只能继承/etc/profile中的变量,他们是”父子”关系.

~/.bash_profile 是交互式、login 方式进入 bash 运行的 ~/.bashrc 是交互式 non-login 方式进入 bash 运行的 通常二者设置大致相同,所以通常前者会调用后者。

让你提升命令行效率的 Bash 快捷键

编辑命令

Ctrl + a :移到命令行首
Ctrl + e :移到命令行尾
Ctrl + f :按字符前移(右向)
Ctrl + b :按字符后移(左向)
Alt + f :按单词前移(右向)
Alt + b :按单词后移(左向)
Ctrl + xx:在命令行首和光标之间移动
Ctrl + u :从光标处删除至命令行首
Ctrl + k :从光标处删除至命令行尾
Ctrl + w :从光标处删除至字首
Alt + d :从光标处删除至字尾
Ctrl + d :删除光标处的字符
Ctrl + h :删除光标前的字符
Ctrl + y :粘贴至光标后
Alt + c :从光标处更改为首字母大写的单词
Alt + u :从光标处更改为全部大写的单词
Alt + l :从光标处更改为全部小写的单词
Ctrl + t :交换光标处和之前的字符
Alt + t :交换光标处和之前的单词
Alt + Backspace:与 Ctrl + w 相同类似,分隔符有些差别 [感谢 rezilla 指正]

重新执行命令

Ctrl + r:逆向搜索命令历史
Ctrl + g:从历史搜索模式退出
Ctrl + p:历史中的上一条命令
Ctrl + n:历史中的下一条命令
Alt + .:使用上一条命令的最后一个参数

控制命令

Ctrl + l:清屏
Ctrl + o:执行当前命令,并选择上一条命令
Ctrl + s:阻止屏幕输出
Ctrl + q:允许屏幕输出
Ctrl + c:终止命令
Ctrl + z:挂起命令

Bang (!) 命令

!!:执行上一条命令
!blah:执行最近的以 blah 开头的命令,如 !ls
!blah:p:仅打印输出,而不执行
!$:上一条命令的最后一个参数,与 Alt + . 相同
!$:p:打印输出 !$ 的内容
!*:上一条命令的所有参数
!*:p:打印输出 !* 的内容
^blah:删除上一条命令中的 blah
^blah^foo:将上一条命令中的 blah 替换为 foo
^blah^foo^:将上一条命令中所有的 blah 都替换为 foo

Avoid-bundle-exec

1.为什么会出现bundle exec的情况?

bundle exec这个前缀,是为了保持本地所云行的gem与gemfile里面指定的gem是一致的,否则,会因为版本问题,出现各种小的bug,有时候让开发者无所适从。 为了,消除bundle exec这个前缀的同时,而让本地完全按照gemfile里面所指定的版本执行,所以有了这篇文章。

2.解决方案:RVM与bundler集成

这也是首选方案,如果在Windows环境下开发的话,这个方法并不适合,强烈建议抛弃windows,否则后患无穷。说正题, 为了能够让RVM正确地自动执行本地环境,需要进行如下步骤 运行

$ rvm get head && rvm reload
$ chmod +x $rvm_path/hooks/after_cd_bundler
$ cd ~/rails_projects/sample_app
$ bundle install --without production --binstubs=./bundler_stubs

这些命令能够神奇地让RVM和Bundler结合在一起,这样就可以确保,像rake和raspec这样的命令,自动并且正确地执行本地环境。

最后, 因为bundler_sutbs是与本地配置有关的文件, 所以最好将其加到.gitignore中

Git-show-file-changed

git diff 工作区与提交任务(提交暂存区,stage)中相比的差异

git diff HEAD 工作区和HEAD(当前工作分支)相比的差异

git diff –cached (或–staged)提交暂存区(提交任务,stage)和版本库中文件的差异

git status 显示状态 -s表示用精简方式 -b同时显示当前工作分支的名称

Open-file-mode

r

Read-only mode. The file pointer is placed at the beginning of the file. 
This is the default mode.

r+

Read-write mode. The file pointer will be at the beginning of the file.

w

Write-only mode. Overwrites the file if the file exists. 
If the file does not exist, creates a new file for writing.

w+

Read-write mode. Overwrites the existing file if the file exists. 
If the file does not exist, creates a new file for reading and writing.

a

Write-only mode. 
The file pointer is at the end of the file if the file exists. 
That is, the file is in the append mode. 
If the file does not exist, it creates a new file for writing.

a+

Read and write mode. 
The file pointer is at the end of the file if the file exists. 
The file opens in the append mode. 
If the file does not exist, it creates a new file for reading and writing.

Textmate-shortcuts

TextMate 快捷键

cmd + option + L    显示行号
cmd + F            页面搜索文字
cmd + shift + F        项目搜索文字
cmd + G            下一个搜索文字
cmd + shift + G    上一个搜索文字
cmd + option + F    替换一个
cmd + ctrl + F        全部替换
cmd + S            保存
cmd + option + S    全部保存    
cmd + shift + S        另存为。。。。
cmd + shift + ->        选中光标右面的内容
cmd + shift + <-        选中光标左面的内容
cmd + shift + L        选取一行信息
cmd + L             定位到某一行
cmd + option + ->    textmate项目中右面的标签
cmd + option + <-    textmate项目中左面的标签
cmd + 数字        选择某个标签
cmd + ->            光标回到行尾
cmd + <-            光标回到行首
cmd + ^            光标回到页首
cmd + 下箭头        光标回到页尾
cmd + /                注释一行
cmd + z            返回前一个内容
cmd + ]                增加缩进
cmd + [                减少缩进
cmd + T            打开项目下的文件
cmd + O            打开项目
cmd + N            新建文件
cmd + W                 关闭标签
cmd + X            剪切
cmd + C            复制
cmd + V            粘帖
cmd + M            最小法
cmd + F2            标记
cmd + shift + z        返回后一个内容
cmd + option + [        格式化代码
cmd + shift + T        当前文件中所有方法的
cmd + shift + W    关闭项目
cmd + option + L      显示行号
cmd + alt + [       代码格式化
cmd + shift + t 当前文件中所有的方法导航

ctrl + A                    光标回到行首
ctrl + E                    光标回到行尾
ctrl + L                    自动生成 =>
ctrl + w                    选取一个单词
ctrl + tab                    在菜单栏和页面切换
ctrl + 上箭头                向上移动下拉菜单
ctrl + 下箭头                向下移动下拉菜单
ctrl + cmd + 上箭头    向上移动整行
ctrl + cmd + 下箭头    向下移动整行
ctrl + shift + k 删除一行代码
ctrl + shift + >  (erb模板) 一键<%= %>,连续按会出现多种效果 
ctrl + option + cmd + V    从历史中选择内容粘帖

option|ctrl + ->            光标向右移动一个单词
option|ctrl + <-            光标向左移动一个单词
option|ctrl + shift + >   选中光标右面的单词
option|ctrl + shift + <   选中光标左面的单词

F2                        在标记间切换
tab     输入def按tab它会自动补全end
esc     以该文件中已经出现过的词做自动补全,可以按多次esc切换单词