diff --git a/Rakefile b/Rakefile index 1026bb482..b7c975da4 100644 --- a/Rakefile +++ b/Rakefile @@ -116,6 +116,7 @@ end desc "Run the specs" task :test => ["clean", "clone-default-bundles"] do + `pkill Atom` Rake::Task["run"].invoke("--test") end diff --git a/native/main_mac.mm b/native/main_mac.mm index 9db015762..565c62342 100644 --- a/native/main_mac.mm +++ b/native/main_mac.mm @@ -83,6 +83,7 @@ void listenForPathToOpen(int fd, NSString *socketPath) { dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ [[AtomApplication sharedApplication] open:path]; + [NSApp activateIgnoringOtherApps:YES]; }); } } diff --git a/native/v8_extensions/onig_reg_exp.mm b/native/v8_extensions/onig_reg_exp.mm index 71c991b14..ba1657b1f 100644 --- a/native/v8_extensions/onig_reg_exp.mm +++ b/native/v8_extensions/onig_reg_exp.mm @@ -76,9 +76,13 @@ bool OnigRegExp::Execute(const CefString& name, return true; } else if (name == "buildOnigRegExp") { - CefRefPtr userData = new OnigRegExpUserData(arguments[0]); + CefRefPtr pattern = arguments[0]; + CefRefPtr userData = new OnigRegExpUserData(pattern); + if (!userData->m_regex) { + exception = std::string("Failed to create OnigRegExp from pattern '") + pattern->GetStringValue().ToString() + "'"; + } retval = CefV8Value::CreateObject(NULL); - retval->SetUserData(userData); + retval->SetUserData((CefRefPtr)userData); return true; } diff --git a/script/clean-merged-branches b/script/clean-merged-branches new file mode 100755 index 000000000..04fbd19c8 --- /dev/null +++ b/script/clean-merged-branches @@ -0,0 +1,41 @@ +#!/bin/sh +#/ Usage: clean-merged-branches [-f] +#/ Delete merged branches from the origin remote. +#/ +#/ Options: +#/ -f Really delete the branches. Without this branches are shown +#/ but nothing is deleted. + +set -e + +# show usage maybe +[ "$1" = "--help" ] && { + grep '^#/' <"$0"| cut -c4- + exit 0 +} + +# fetch and prune remote branches +git fetch origin --prune + +# grab list of merged branches +branches=$( + git branch -a --merged origin/master | + grep remotes/origin/ | + grep -v /master | + sed 's@remotes/origin/@@' +) + +# bail out with no branches +[ -z "$branches" ] && { + echo "no merged branches detected" 1>&2 + exit 0 +} + +# delete the branches or just show what would be done without -f +if [ "$1" = -f ]; then + git push origin $(echo "$branches" | sed 's/^ */:/') +else + echo "These branches will be deleted:" 1>&2 + echo "$branches" + echo "Run \`$0 -f' if you're sure." +fi diff --git a/script/update-cef b/script/update-cef index cd828418b..d58b03a0b 100755 --- a/script/update-cef +++ b/script/update-cef @@ -19,11 +19,11 @@ CEF_RELEASE_BRANCH=${2:-1271} CEF_BINARY_PATH=$(echo "$CEF_DIR"/binary_distrib/cef_binary_*_macosx/) # Expand the path # Update and compile CEF -#CEF_AUTOMATE_SCRIPT_PATH=/tmp/cef-update -#rm -rf $CEF_AUTOMATE_SCRIPT_PATH -#svn checkout http://chromiumembedded.googlecode.com/svn/trunk/cef1/tools/automate $CEF_AUTOMATE_SCRIPT_PATH -#cd $CEF_AUTOMATE_SCRIPT_PATH -#python automate.py --download-dir="$(dirname $CHROMIUM_DIR)" --url=http://chromiumembedded.googlecode.com/svn/branches/$CEF_RELEASE_BRANCH/cef3 +CEF_AUTOMATE_SCRIPT_PATH=/tmp/cef-update +rm -rf $CEF_AUTOMATE_SCRIPT_PATH +svn checkout http://chromiumembedded.googlecode.com/svn/trunk/cef1/tools/automate $CEF_AUTOMATE_SCRIPT_PATH +cd $CEF_AUTOMATE_SCRIPT_PATH +python automate.py --download-dir="$(dirname $CHROMIUM_DIR)" --url=http://chromiumembedded.googlecode.com/svn/branches/$CEF_RELEASE_BRANCH/cef3 # Copy the CEF gypi files and update their paths to match Atom's file layout for GYPI_PATH in "$CEF_DIR/cef_paths.gypi" "$CEF_DIR/cef_paths2.gypi"; do diff --git a/spec/app/text-mate-grammar-spec.coffee b/spec/app/text-mate-grammar-spec.coffee index e694eb302..351724bd6 100644 --- a/spec/app/text-mate-grammar-spec.coffee +++ b/spec/app/text-mate-grammar-spec.coffee @@ -197,25 +197,27 @@ describe "TextMateGrammar", -> expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"] expect(tokens[1]).toEqual value: 'div', scopes: ["text.html.ruby","meta.tag.block.any.html","entity.name.tag.block.any.html"] - expect(tokens[2]).toEqual value: ' class=', scopes: ["text.html.ruby","meta.tag.block.any.html"] - expect(tokens[3]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.begin.html"] - expect(tokens[4]).toEqual value: 'name', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html"] - expect(tokens[5]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.end.html"] - expect(tokens[6]).toEqual value: '>', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"] - expect(tokens[7]).toEqual value: '<%=', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"] - expect(tokens[8]).toEqual value: ' ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"] - expect(tokens[9]).toEqual value: 'User', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","support.class.ruby"] - expect(tokens[10]).toEqual value: '.', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.separator.method.ruby"] - expect(tokens[11]).toEqual value: 'find', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"] - expect(tokens[12]).toEqual value: '(', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.function.ruby"] - expect(tokens[13]).toEqual value: '2', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","constant.numeric.ruby"] - expect(tokens[14]).toEqual value: ')', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.function.ruby"] - expect(tokens[15]).toEqual value: '.', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.separator.method.ruby"] - expect(tokens[16]).toEqual value: 'full_name ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"] - expect(tokens[17]).toEqual value: '%>', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"] - expect(tokens[18]).toEqual value: '', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"] + expect(tokens[2]).toEqual value: ' ', scopes: ["text.html.ruby","meta.tag.block.any.html"] + expect(tokens[3]).toEqual value: 'class', scopes: ["text.html.ruby","meta.tag.block.any.html", "entity.other.attribute-name.html"] + expect(tokens[4]).toEqual value: '=', scopes: ["text.html.ruby","meta.tag.block.any.html"] + expect(tokens[5]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.begin.html"] + expect(tokens[6]).toEqual value: 'name', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html"] + expect(tokens[7]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.end.html"] + expect(tokens[8]).toEqual value: '>', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"] + expect(tokens[9]).toEqual value: '<%=', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"] + expect(tokens[10]).toEqual value: ' ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"] + expect(tokens[11]).toEqual value: 'User', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","support.class.ruby"] + expect(tokens[12]).toEqual value: '.', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.separator.method.ruby"] + expect(tokens[13]).toEqual value: 'find', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"] + expect(tokens[14]).toEqual value: '(', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.function.ruby"] + expect(tokens[15]).toEqual value: '2', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","constant.numeric.ruby"] + expect(tokens[16]).toEqual value: ')', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.function.ruby"] + expect(tokens[17]).toEqual value: '.', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.separator.method.ruby"] + expect(tokens[18]).toEqual value: 'full_name ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"] + expect(tokens[19]).toEqual value: '%>', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"] + expect(tokens[20]).toEqual value: '', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"] it "can parse a grammar with newline charachters in its regular expressions (regression)", -> grammar = new TextMateGrammar @@ -244,3 +246,15 @@ describe "TextMateGrammar", -> grammar = TextMateBundle.grammarForFilePath("hello.js") {tokens, ruleStack} = grammar.tokenizeLine("// line comment") {tokens, ruleStack} = grammar.tokenizeLine(" // second line comment with a single leading space", ruleStack) + + describe "when inside an C block", -> + it "correctly parses a method. (regression)", -> + grammar = TextMateBundle.grammarForFilePath("hello.c") + {tokens, ruleStack} = grammar.tokenizeLine("if(1){m()}") + expect(tokens[5]).toEqual value: "m", scopes: ["source.c", "meta.block.c", "meta.function-call.c", "support.function.any-method.c"] + + it "correctly parses nested blocks. (regression)", -> + grammar = TextMateBundle.grammarForFilePath("hello.c") + {tokens, ruleStack} = grammar.tokenizeLine("if(1){if(1){m()}}") + expect(tokens[5]).toEqual value: "if", scopes: ["source.c", "meta.block.c", "keyword.control.c"] + expect(tokens[10]).toEqual value: "m", scopes: ["source.c", "meta.block.c", "meta.block.c", "meta.function-call.c", "support.function.any-method.c"] diff --git a/spec/app/undo-manager-spec.coffee b/spec/app/undo-manager-spec.coffee index 307412a74..d4933f612 100644 --- a/spec/app/undo-manager-spec.coffee +++ b/spec/app/undo-manager-spec.coffee @@ -98,3 +98,57 @@ describe "UndoManager", -> undoManager.undo() expect(buffer.lineForRow(0)).not.toContain("foo") + it "records transactions that occur prior to an exception", -> + spyOn(console, 'error') + buffer.setText("jumpstreet") + undoManager.transact -> + buffer.insert([0,0], "3") + buffer.insert([0,0], "2") + throw new Error("problem") + buffer.insert([0,0], "2") + + expect(console.error).toHaveBeenCalled() + expect(buffer.lineForRow(0)).toBe "23jumpstreet" + undoManager.undo() + expect(buffer.lineForRow(0)).toBe "jumpstreet" + + describe "when a `do` operation throws an exception", -> + it "clears the stack", -> + spyOn(console, 'error') + buffer.setText("word") + class FailingOperation + do: -> throw new Error("I'm a bad do operation") + + buffer.insert([0,0], "1") + undoManager.pushOperation(new FailingOperation()) + expect(console.error).toHaveBeenCalled() + undoManager.undo() + expect(buffer.lineForRow(0)).toBe "1word" + + + describe "when an `undo` operation throws an exception", -> + it "clears the stack", -> + spyOn(console, 'error') + buffer.setText("word") + class FailingOperation + undo: -> throw new Error("I'm a bad undo operation") + + buffer.insert([0,0], "1") + undoManager.pushOperation(new FailingOperation()) + undoManager.undo() + expect(console.error).toHaveBeenCalled() + expect(buffer.lineForRow(0)).toBe "1word" + + describe "when an `redo` operation throws an exception", -> + it "clears the stack", -> + spyOn(console, 'error') + buffer.setText("word") + class FailingOperation + redo: -> throw new Error("I'm a bad undo operation") + + buffer.insert([0,0], "1") + undoManager.pushOperation(new FailingOperation()) + undoManager.undo() + undoManager.redo() + expect(console.error).toHaveBeenCalled() + expect(buffer.lineForRow(0)).toBe "1word" \ No newline at end of file diff --git a/src/app/text-mate-grammar.coffee b/src/app/text-mate-grammar.coffee index 38549d0a7..4c0a07cc3 100644 --- a/src/app/text-mate-grammar.coffee +++ b/src/app/text-mate-grammar.coffee @@ -26,6 +26,7 @@ class TextMateGrammar @firstLineRegex = new OnigRegExp(firstLineMatch) if firstLineMatch for name, data of repository + data = {patterns: [data], tempName: name} if data.begin? or data.match? @repository[name] = new Rule(this, data) tokenizeLine: (line, {ruleStack, tabLength}={}) -> @@ -74,7 +75,7 @@ class TextMateGrammar ruleForInclude: (name) -> if name[0] == "#" @repository[name[1..]] - else if name == "$self" + else if name == "$self" or name == "$base" @initialRule else TextMateBundle = require 'text-mate-bundle' @@ -111,7 +112,6 @@ class Rule # Add a `\n` to appease patterns that contain '\n' explicitly return null unless result = @getScanner().findNextMatch(line + "\n", position) { index, captureIndices } = result - # Since the `\n' (added above) is not part of the line, truncate captures to the line's actual length lineLength = line.length captureIndices = captureIndices.map (value, index) -> diff --git a/src/app/undo-manager.coffee b/src/app/undo-manager.coffee index 21760ef62..d3e4b8273 100644 --- a/src/app/undo-manager.coffee +++ b/src/app/undo-manager.coffee @@ -8,7 +8,10 @@ class UndoManager currentTransaction: null constructor: -> - @startBatchCallCount = 0 + @clear() + + clear: -> + @currentTransaction = null @undoHistory = [] @redoHistory = [] @@ -18,29 +21,50 @@ class UndoManager else @undoHistory.push([operation]) @redoHistory = [] - operation.do?(editSession) + + try + operation.do?(editSession) + catch e + console.error e.stack + @clear() transact: (fn) -> + safeFn = -> + try + fn() + catch e + console.error e.stack + if @currentTransaction - fn() + safeFn() else @currentTransaction = [] - fn() - @undoHistory.push(@currentTransaction) if @currentTransaction.length + safeFn() + @undoHistory.push(@currentTransaction) if @currentTransaction?.length @currentTransaction = null undo: (editSession) -> - if batch = @undoHistory.pop() - opsInReverse = new Array(batch...) - opsInReverse.reverse() - op.undo?(editSession) for op in opsInReverse - @redoHistory.push batch - batch.oldSelectionRanges + try + if batch = @undoHistory.pop() + opsInReverse = new Array(batch...) + opsInReverse.reverse() + op.undo?(editSession) for op in opsInReverse + + @redoHistory.push batch + batch.oldSelectionRanges + catch e + console.error e.stack + @clear() redo: (editSession) -> - if batch = @redoHistory.pop() - for op in batch - op.do?(editSession) - op.redo?(editSession) - @undoHistory.push(batch) - batch.newSelectionRanges + try + if batch = @redoHistory.pop() + for op in batch + op.do?(editSession) + op.redo?(editSession) + + @undoHistory.push(batch) + batch.newSelectionRanges + catch e + console.error e.stack + @clear()