From dc9a343b9dc533ddc40145508f7053ae6721df2e Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 1 Feb 2012 11:44:52 -0800 Subject: [PATCH 01/12] little fixes to the custom webkit doc --- docs/webkit.md | 476 +------------------------------------------------ 1 file changed, 8 insertions(+), 468 deletions(-) diff --git a/docs/webkit.md b/docs/webkit.md index 0719c8ac7..b3062c187 100644 --- a/docs/webkit.md +++ b/docs/webkit.md @@ -5,476 +5,16 @@ * Apply tmm1's patch (found at end of this document) `patch -p1 < tmm1.patch` -* Build webkit (~2 hours) - `Tools/Scripts/build-webkit` +* Build webkit (~2 hours, don't let your computer go to sleep) + `Tools/Scripts/build-webkit --release` +* Copy WebKit.framework, WebCore.framework and JavaScript.framework from `webkit/WebKitBuild/Release` to +`atom/frameworks` + +* Fix the dynamic library linking problems + `rake webkit-fix` # tmm1's patch ``` - -diff --git a/LayoutTests/fast/js/exception-properties-expected.txt b/LayoutTests/fast/js/exception-properties-expected.txt -index 36d7e84..573ef10 100644 ---- a/LayoutTests/fast/js/exception-properties-expected.txt -+++ b/LayoutTests/fast/js/exception-properties-expected.txt -@@ -4,7 +4,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE - - - PASS enumerableProperties(error) is [] --PASS enumerableProperties(nativeError) is ["line", "sourceId", "sourceURL"] -+PASS enumerableProperties(nativeError) is ["line", "sourceId", "sourceURL", "stack"] - PASS Object.getPrototypeOf(nativeError).name is "RangeError" - PASS Object.getPrototypeOf(nativeError).message is "" - PASS successfullyParsed is true -diff --git a/LayoutTests/fast/js/script-tests/exception-properties.js b/LayoutTests/fast/js/script-tests/exception-properties.js -index 30789a0..f7c2871 100644 ---- a/LayoutTests/fast/js/script-tests/exception-properties.js -+++ b/LayoutTests/fast/js/script-tests/exception-properties.js -@@ -16,7 +16,7 @@ try { - var error = new Error("message"); - - shouldBe('enumerableProperties(error)', '[]'); -- shouldBe('enumerableProperties(nativeError)', '["line", "sourceId", "sourceURL"]'); -+ shouldBe('enumerableProperties(nativeError)', '["line", "sourceId", "sourceURL", "jscStack"]'); - - shouldBe('Object.getPrototypeOf(nativeError).name', '"RangeError"'); - shouldBe('Object.getPrototypeOf(nativeError).message', '""'); -diff --git a/LayoutTests/platform/chromium/test_expectations.txt b/LayoutTests/platform/chromium/test_expectations.txt -index f6e35e0..e6d0b90 100644 ---- a/LayoutTests/platform/chromium/test_expectations.txt -+++ b/LayoutTests/platform/chromium/test_expectations.txt -@@ -518,6 +518,10 @@ WONTFIX SKIP : fast/frames/cross-site-this.html = FAIL - // throw. V8 follows the spec. - WONTFIX SKIP : fast/js/reparsing-semicolon-insertion.html = FAIL - -+// This tests stack-traces that are generated by JSC. This test should -+// fail since it is specific to jsc. -+WONTFIX SKIP : fast/js/stack-trace.html = FAIL -+ - // Rubber-banding is currently a CG only feature. - WONTFIX : platform/chromium/rubberbanding = FAIL - WONTFIX : platform/chromium/compositing/rubberbanding = IMAGE -diff --git a/Source/JavaScriptCore/JavaScriptCore.exp b/Source/JavaScriptCore/JavaScriptCore.exp -index 4bf3ed9..1d11aea 100644 ---- a/Source/JavaScriptCore/JavaScriptCore.exp -+++ b/Source/JavaScriptCore/JavaScriptCore.exp -@@ -117,6 +117,7 @@ __ZN3JSC10JSFunction6s_infoE - __ZN3JSC10JSFunctionC1EPNS_9ExecStateEPNS_14JSGlobalObjectEPNS_9StructureE - __ZN3JSC10throwErrorEPNS_9ExecStateENS_7JSValueE - __ZN3JSC10throwErrorEPNS_9ExecStateEPNS_8JSObjectE -+__ZN3JSC11Interpreter13getStackTraceEPNS_12JSGlobalDataEiRN3WTF6VectorINS_10StackFrameELm0EEE - __ZN3JSC11JSByteArray10putByIndexEPNS_6JSCellEPNS_9ExecStateEjNS_7JSValueE - __ZN3JSC11JSByteArray15createStructureERNS_12JSGlobalDataEPNS_14JSGlobalObjectENS_7JSValueEPKNS_9ClassInfoE - __ZN3JSC11JSByteArray18getOwnPropertySlotEPNS_6JSCellEPNS_9ExecStateERKNS_10IdentifierERNS_12PropertySlotE -diff --git a/Source/JavaScriptCore/JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.def b/Source/JavaScriptCore/JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.def -index 5095d5c..13ee907 100644 ---- a/Source/JavaScriptCore/JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.def -+++ b/Source/JavaScriptCore/JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.def -@@ -208,6 +208,7 @@ EXPORTS - ?getPropertyDescriptor@JSObject@JSC@@QAE_NPAVExecState@2@ABVIdentifier@2@AAVPropertyDescriptor@2@@Z - ?getPropertyNames@JSObject@JSC@@SAXPAV12@PAVExecState@2@AAVPropertyNameArray@2@W4EnumerationMode@2@@Z - ?getSlice@ArgList@JSC@@QBEXHAAV12@@Z -+ ?getStackTrace@Interpreter@JSC@@SAXPAVJSGlobalData@2@HAAV?$Vector@UStackFrame@JSC@@$0A@@WTF@@@Z - ?getString@JSCell@JSC@@QBE?AVUString@2@PAVExecState@2@@Z - ?getString@JSCell@JSC@@QBE_NPAVExecState@2@AAVUString@2@@Z - ?getter@PropertyDescriptor@JSC@@QBE?AVJSValue@2@XZ -diff --git a/Source/JavaScriptCore/interpreter/Interpreter.cpp b/Source/JavaScriptCore/interpreter/Interpreter.cpp -index 4194901..b9d07dd 100644 ---- a/Source/JavaScriptCore/interpreter/Interpreter.cpp -+++ b/Source/JavaScriptCore/interpreter/Interpreter.cpp -@@ -45,7 +45,6 @@ - #include "JSActivation.h" - #include "JSArray.h" - #include "JSByteArray.h" --#include "JSFunction.h" - #include "JSNotAnObject.h" - #include "JSPropertyNameIterator.h" - #include "LiteralParser.h" -@@ -790,6 +789,95 @@ static void appendSourceToError(CallFrame* callFrame, ErrorInstance* exception, - exception->putDirect(*globalData, globalData->propertyNames->message, jsString(globalData, message)); - } - -+static void getCallerLine(JSGlobalData* globalData, CallFrame* callFrame, int& lineNumber) -+{ -+ (void)globalData; -+ unsigned bytecodeOffset; -+ lineNumber = -1; -+ callFrame = callFrame->removeHostCallFrameFlag(); -+ -+ if (callFrame->callerFrame() == CallFrame::noCaller() || callFrame->callerFrame()->hasHostCallFrameFlag()) -+ return; -+ -+ CodeBlock* callerCodeBlock = callFrame->callerFrame()->removeHostCallFrameFlag()->codeBlock(); -+ -+#if ENABLE(INTERPRETER) -+ if (!globalData->canUseJIT()) -+ bytecodeOffset = callerCodeBlock->bytecodeOffset(callFrame->returnVPC()); -+#if ENABLE(JIT) -+ else -+ bytecodeOffset = callerCodeBlock->bytecodeOffset(callFrame->returnPC()); -+#endif -+#else -+ bytecodeOffset = callerCodeBlock->bytecodeOffset(callFrame->returnPC()); -+#endif -+ -+ lineNumber = callerCodeBlock->lineNumberForBytecodeOffset(bytecodeOffset - 1); -+} -+ -+static ALWAYS_INLINE const UString getSourceURLFromCallFrame(CallFrame* callFrame) -+{ -+ if (callFrame->hasHostCallFrameFlag()) -+ return UString(); -+#if ENABLE(INTERPRETER) -+ if (!callFrame->globalData().canUseJIT()) -+ return callFrame->codeBlock()->source()->url(); -+#if ENABLE(JIT) -+ return callFrame->codeBlock()->ownerExecutable()->sourceURL(); -+#endif -+#else -+ return callFrame->codeBlock()->ownerExecutable()->sourceURL(); -+#endif -+} -+ -+static StackFrameCodeType getStackFrameCodeType(CallFrame* callFrame) -+{ -+ if (callFrame->hasHostCallFrameFlag()) -+ return StackFrameNativeCode; -+ -+ switch (callFrame->codeBlock()->codeType()) { -+ case EvalCode: -+ return StackFrameEvalCode; -+ case FunctionCode: -+ return StackFrameFunctionCode; -+ case GlobalCode: -+ return StackFrameGlobalCode; -+ } -+ ASSERT_NOT_REACHED(); -+ return StackFrameGlobalCode; -+} -+ -+void Interpreter::getStackTrace(JSGlobalData* globalData, int line, Vector& results) -+{ -+ int stackLimit = 32; -+ CallFrame* callFrame = globalData->topCallFrame->removeHostCallFrameFlag(); -+ if (!callFrame || callFrame == CallFrame::noCaller() || !callFrame->codeBlock()) -+ return; -+ UString sourceURL; -+ UString traceLevel; -+ -+ for (int i = 0; i < stackLimit; ++i) { -+ if (!callFrame || callFrame == CallFrame::noCaller()) -+ break; -+ if (callFrame->codeBlock()) { -+ sourceURL = getSourceURLFromCallFrame(callFrame); -+ -+ StackFrame s = { -+ Strong(*globalData, callFrame->callee()), -+ Strong(*globalData, callFrame), -+ getStackFrameCodeType(callFrame), -+ Strong(*globalData, callFrame->codeBlock()->ownerExecutable()), -+ line, -+ sourceURL -+ }; -+ -+ results.append(s); -+ } -+ getCallerLine(globalData, callFrame, line); -+ callFrame = callFrame->callerFrame()->removeHostCallFrameFlag(); -+ } -+} -+ - NEVER_INLINE HandlerInfo* Interpreter::throwException(CallFrame*& callFrame, JSValue& exceptionValue, unsigned bytecodeOffset) - { - CodeBlock* codeBlock = callFrame->codeBlock(); -@@ -808,7 +896,9 @@ NEVER_INLINE HandlerInfo* Interpreter::throwException(CallFrame*& callFrame, JSV - - // FIXME: should only really be adding these properties to VM generated exceptions, - // but the inspector currently requires these for all thrown objects. -- addErrorInfo(callFrame, exception, codeBlock->lineNumberForBytecodeOffset(bytecodeOffset), codeBlock->ownerExecutable()->source()); -+ Vector stackTrace; -+ getStackTrace(&callFrame->globalData(), codeBlock->lineNumberForBytecodeOffset(bytecodeOffset), stackTrace); -+ addErrorInfo(callFrame, exception, codeBlock->lineNumberForBytecodeOffset(bytecodeOffset), codeBlock->ownerExecutable()->source(), stackTrace); - } - - isInterrupt = isInterruptedExecutionException(exception) || isTerminatedExecutionException(exception); -diff --git a/Source/JavaScriptCore/interpreter/Interpreter.h b/Source/JavaScriptCore/interpreter/Interpreter.h -index 6dfd331..748e8fb 100644 ---- a/Source/JavaScriptCore/interpreter/Interpreter.h -+++ b/Source/JavaScriptCore/interpreter/Interpreter.h -@@ -31,10 +31,12 @@ - - #include "ArgList.h" - #include "JSCell.h" -+#include "JSFunction.h" - #include "JSValue.h" - #include "JSObject.h" - #include "Opcode.h" - #include "RegisterFile.h" -+#include "StrongInlines.h" - - #include - -@@ -42,8 +44,8 @@ namespace JSC { - - class CodeBlock; - class EvalExecutable; -+ class ExecutableBase; - class FunctionExecutable; -- class JSFunction; - class JSGlobalObject; - class ProgramExecutable; - class Register; -@@ -62,16 +64,79 @@ namespace JSC { - WillExecuteStatement - }; - -+ enum StackFrameCodeType { -+ StackFrameGlobalCode, -+ StackFrameEvalCode, -+ StackFrameFunctionCode, -+ StackFrameNativeCode -+ }; -+ -+ struct StackFrame { -+ Strong callee; -+ Strong callFrame; -+ StackFrameCodeType codeType; -+ Strong executable; -+ int line; -+ UString sourceURL; -+ UString toString() const -+ { -+ bool hasSourceURLInfo = !sourceURL.isNull() && !sourceURL.isEmpty(); -+ bool hasLineInfo = line > -1; -+ String traceLine; -+ String sourceInfo; -+ JSObject* stackFrameCallee = callee.get(); -+ UString functionName = ""; -+ -+ if (hasSourceURLInfo) -+ sourceInfo = hasLineInfo ? String::format("%s:%d", sourceURL.ascii().data(), line) -+ : String::format("%s", sourceURL.ascii().data()); -+ -+ if (stackFrameCallee && stackFrameCallee->inherits(&JSFunction::s_info)) -+ functionName = asFunction(stackFrameCallee)->name(callFrame.get()); -+ -+ switch (codeType) { -+ case StackFrameEvalCode: -+ if (hasSourceURLInfo && !functionName.isEmpty()) -+ traceLine = String::format("at eval at %s (%s)", functionName.ascii().data(), sourceInfo.ascii().data()); -+ else if (hasSourceURLInfo) -+ traceLine = String::format("at eval at (%s)", sourceInfo.ascii().data()); -+ else if (!functionName.isEmpty()) -+ traceLine = String::format("at eval at %s", functionName.ascii().data()); -+ else -+ traceLine = String::format("at eval"); -+ break; -+ case StackFrameNativeCode: -+ if (!functionName.isEmpty()) -+ traceLine = String::format("at %s (native)", functionName.ascii().data()); -+ else -+ traceLine = "at (native)"; -+ break; -+ case StackFrameFunctionCode: -+ case StackFrameGlobalCode: -+ if (hasSourceURLInfo && !functionName.isEmpty()) -+ traceLine = String::format("at %s (%s)", functionName.ascii().data(), sourceInfo.ascii().data()); -+ else if (hasSourceURLInfo) -+ traceLine = String::format("at %s", sourceInfo.ascii().data()); -+ else if (!functionName.isEmpty()) -+ traceLine = String::format("at %s", functionName.ascii().data()); -+ else -+ traceLine = String::format("at unknown source"); -+ break; -+ } -+ return traceLine.impl(); -+ } -+ }; -+ - class TopCallFrameSetter { - public: - TopCallFrameSetter(JSGlobalData& global, CallFrame* callFrame) - : globalData(global) -- , oldCallFrame(global.topCallFrame) -+ , oldCallFrame(global.topCallFrame) - { - global.topCallFrame = callFrame; - } -- -- ~TopCallFrameSetter() -+ -+ ~TopCallFrameSetter() - { - globalData.topCallFrame = oldCallFrame; - } -@@ -141,6 +206,8 @@ namespace JSC { - - NEVER_INLINE HandlerInfo* throwException(CallFrame*&, JSValue&, unsigned bytecodeOffset); - NEVER_INLINE void debug(CallFrame*, DebugHookID, int firstLine, int lastLine); -+ static const UString getTraceLine(CallFrame*, StackFrameCodeType, const UString&, int); -+ static void getStackTrace(JSGlobalData*, int line, Vector& results); - - void dumpSampleData(ExecState* exec); - void startSampling(); -diff --git a/Source/JavaScriptCore/jsc.cpp b/Source/JavaScriptCore/jsc.cpp -index 47ec8c6..6d82f54 100644 ---- a/Source/JavaScriptCore/jsc.cpp -+++ b/Source/JavaScriptCore/jsc.cpp -@@ -27,6 +27,7 @@ - #include "CurrentTime.h" - #include "ExceptionHelpers.h" - #include "InitializeThreading.h" -+#include "Interpreter.h" - #include "JSArray.h" - #include "JSFunction.h" - #include "JSLock.h" -@@ -78,6 +79,7 @@ static bool fillBufferWithContentsOfFile(const UString& fileName, Vector& - - static EncodedJSValue JSC_HOST_CALL functionPrint(ExecState*); - static EncodedJSValue JSC_HOST_CALL functionDebug(ExecState*); -+static EncodedJSValue JSC_HOST_CALL functionJSCStack(ExecState*); - static EncodedJSValue JSC_HOST_CALL functionGC(ExecState*); - #ifndef NDEBUG - static EncodedJSValue JSC_HOST_CALL functionReleaseExecutableMemory(ExecState*); -@@ -184,6 +186,7 @@ protected: - addFunction(globalData, "run", functionRun, 1); - addFunction(globalData, "load", functionLoad, 1); - addFunction(globalData, "checkSyntax", functionCheckSyntax, 1); -+ addFunction(globalData, "jscStack", functionJSCStack, 1); - addFunction(globalData, "readline", functionReadline, 0); - addFunction(globalData, "preciseTime", functionPreciseTime, 0); - #if ENABLE(SAMPLING_FLAGS) -@@ -252,6 +255,22 @@ EncodedJSValue JSC_HOST_CALL functionDebug(ExecState* exec) - return JSValue::encode(jsUndefined()); - } - -+EncodedJSValue JSC_HOST_CALL functionJSCStack(ExecState* exec) -+{ -+ String trace = "--> Stack trace:\n"; -+ Vector stackTrace; -+ Interpreter::getStackTrace(&exec->globalData(), -1, stackTrace); -+ int i = 0; -+ -+ for (Vector::iterator iter = stackTrace.begin(); iter < stackTrace.end(); iter++) { -+ StackFrame level = *iter; -+ trace += String::format(" %i %s\n", i, level.toString().utf8().data()); -+ i++; -+ } -+ fprintf(stderr, "%s", trace.utf8().data()); -+ return JSValue::encode(jsUndefined()); -+} -+ - EncodedJSValue JSC_HOST_CALL functionGC(ExecState* exec) - { - JSLock lock(SilenceAssertionsOnly); -diff --git a/Source/JavaScriptCore/parser/Parser.h b/Source/JavaScriptCore/parser/Parser.h -index f3d96ff..27dafae 100644 ---- a/Source/JavaScriptCore/parser/Parser.h -+++ b/Source/JavaScriptCore/parser/Parser.h -@@ -1016,7 +1016,7 @@ PassRefPtr Parser::parse(JSGlobalObject* lexicalGlobalObj - else if (isEvalNode()) - *exception = createSyntaxError(lexicalGlobalObject, errMsg); - else -- *exception = addErrorInfo(&lexicalGlobalObject->globalData(), createSyntaxError(lexicalGlobalObject, errMsg), errLine, *m_source); -+ *exception = addErrorInfo(&lexicalGlobalObject->globalData(), createSyntaxError(lexicalGlobalObject, errMsg), errLine, *m_source, Vector()); - } - - if (debugger && !ParsedNode::scopeIsFunction) -diff --git a/Source/JavaScriptCore/runtime/CommonIdentifiers.h b/Source/JavaScriptCore/runtime/CommonIdentifiers.h -index 08d8644..ce11730 100644 ---- a/Source/JavaScriptCore/runtime/CommonIdentifiers.h -+++ b/Source/JavaScriptCore/runtime/CommonIdentifiers.h -@@ -52,6 +52,7 @@ - macro(input) \ - macro(isArray) \ - macro(isPrototypeOf) \ -+ macro(stack) \ - macro(length) \ - macro(message) \ - macro(multiline) \ -diff --git a/Source/JavaScriptCore/runtime/Error.cpp b/Source/JavaScriptCore/runtime/Error.cpp -index c3f2878..9b4af6f 100644 ---- a/Source/JavaScriptCore/runtime/Error.cpp -+++ b/Source/JavaScriptCore/runtime/Error.cpp -@@ -27,6 +27,7 @@ - #include "ConstructData.h" - #include "ErrorConstructor.h" - #include "FunctionPrototype.h" -+#include "JSArray.h" - #include "JSFunction.h" - #include "JSGlobalObject.h" - #include "JSObject.h" -@@ -39,6 +40,8 @@ namespace JSC { - static const char* linePropertyName = "line"; - static const char* sourceIdPropertyName = "sourceId"; - static const char* sourceURLPropertyName = "sourceURL"; -+static const char* stackPropertyName = "stack"; -+static const char* messagePropertyName = "message"; - - JSObject* createError(JSGlobalObject* globalObject, const UString& message) - { -@@ -117,7 +120,7 @@ JSObject* createURIError(ExecState* exec, const UString& message) - return createURIError(exec->lexicalGlobalObject(), message); - } - --JSObject* addErrorInfo(JSGlobalData* globalData, JSObject* error, int line, const SourceCode& source) -+JSObject* addErrorInfo(JSGlobalData* globalData, JSObject* error, int line, const SourceCode& source, const Vector& stackTrace) - { - intptr_t sourceID = source.provider()->asID(); - const UString& sourceURL = source.provider()->url(); -@@ -128,13 +131,29 @@ JSObject* addErrorInfo(JSGlobalData* globalData, JSObject* error, int line, cons - error->putDirect(*globalData, Identifier(globalData, sourceIdPropertyName), jsNumber((double)sourceID), ReadOnly | DontDelete); - if (!sourceURL.isNull()) - error->putDirect(*globalData, Identifier(globalData, sourceURLPropertyName), jsString(globalData, sourceURL), ReadOnly | DontDelete); -+ if (!stackTrace.isEmpty()) { -+ String trace = String::format("%s: ", JSObject::className(error).ascii().data()); -+ trace += asString(error->getDirect(*globalData, Identifier(globalData, messagePropertyName)))->tryGetValue().ascii().data(); -+ trace += "\n"; -+ //JSArray* stackTraceArray = JSArray::create(*globalData, globalData->dynamicGlobalObject->arrayStructure()); -+ for (unsigned i = 0; i < stackTrace.size(); i++) { -+ UString stackLevel = stackTrace[i].toString(); -+ trace += " "; -+ trace += stackLevel.ascii().data(); -+ if (i < stackTrace.size() - 1) -+ trace += "\n"; -+ //stackTraceArray->push(globalData->topCallFrame, jsString(globalData, stackLevel)); -+ } -+ //error->putDirect(*globalData, Identifier(globalData, stackPropertyName), stackTraceArray, ReadOnly | DontDelete); -+ error->putDirect(*globalData, Identifier(globalData, stackPropertyName), jsString(globalData, UString(trace.impl())), ReadOnly | DontDelete); -+ } - - return error; - } - --JSObject* addErrorInfo(ExecState* exec, JSObject* error, int line, const SourceCode& source) -+JSObject* addErrorInfo(ExecState* exec, JSObject* error, int line, const SourceCode& source, const Vector& stackTrace) - { -- return addErrorInfo(&exec->globalData(), error, line, source); -+ return addErrorInfo(&exec->globalData(), error, line, source, stackTrace); - } - - bool hasErrorInfo(ExecState* exec, JSObject* error) -diff --git a/Source/JavaScriptCore/runtime/Error.h b/Source/JavaScriptCore/runtime/Error.h -index 88b540a..59b3949 100644 ---- a/Source/JavaScriptCore/runtime/Error.h -+++ b/Source/JavaScriptCore/runtime/Error.h -@@ -24,6 +24,7 @@ - #define Error_h - - #include "InternalFunction.h" -+#include "Interpreter.h" - #include "JSObject.h" - #include - -@@ -56,9 +57,9 @@ namespace JSC { - - // Methods to add - bool hasErrorInfo(ExecState*, JSObject* error); -- JSObject* addErrorInfo(JSGlobalData*, JSObject* error, int line, const SourceCode&); -+ JSObject* addErrorInfo(JSGlobalData*, JSObject* error, int line, const SourceCode&, const Vector&); - // ExecState wrappers. -- JSObject* addErrorInfo(ExecState*, JSObject* error, int line, const SourceCode&); -+ JSObject* addErrorInfo(ExecState*, JSObject* error, int line, const SourceCode&, const Vector&); - - // Methods to throw Errors. - JS_EXPORT_PRIVATE JSValue throwError(ExecState*, JSValue); +Corey needs to go on the mini server and create the patch based on tmm1's changes ``` From cc3b5732e7f3c3fc7a9712d7a7d3733854aa3ff6 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 1 Feb 2012 15:54:28 -0800 Subject: [PATCH 02/12] Point.toEqual will return true if it matches an array `new Point(1,2)` is equivalent to `[1,2]`. --- spec/atom/point-spec.coffee | 11 +++++++++++ src/atom/point.coffee | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/atom/point-spec.coffee b/spec/atom/point-spec.coffee index c4d095775..0b6852e3b 100644 --- a/spec/atom/point-spec.coffee +++ b/spec/atom/point-spec.coffee @@ -1,6 +1,17 @@ Point = require 'point' describe "Point", -> + describe ".isEqual(value)", -> + describe "when given value is a Point", -> + it "returns true when the rows and columns match", -> + expect(new Point(1,2)).toEqual new Point(1,2) + expect(new Point(2,1)).not.toEqual new Point(1,2) + + describe "when given value is an Array", -> + it "returns true only when index zero matches row and index one matches column", -> + expect(new Point(1,2)).toEqual [1,2] + expect(new Point(2,1)).not.toEqual [1,2] + describe "compare", -> it "returns 1, 0, or -1 based on whether the given point precedes, equals, or follows the receivers location in the buffer", -> expect(new Point(5, 0).compare(new Point(5, 0))).toBe 0 diff --git a/src/atom/point.coffee b/src/atom/point.coffee index 8e1076f9a..3c3f7d103 100644 --- a/src/atom/point.coffee +++ b/src/atom/point.coffee @@ -14,7 +14,10 @@ class Point constructor: (@row, @column) -> isEqual: (other) -> - @row == other.row && @column == other.column + if other instanceof Array + @row == other[0] and @column == other[1] + else + @row == other.row and @column == other.column compare: (other) -> if @row > other.row From 8fb219af4741da20c820027b80248274460e3305 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 1 Feb 2012 15:56:23 -0800 Subject: [PATCH 03/12] Define backspace and delete in terms of deleteLeft and deleteRight. --- src/atom/editor.coffee | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index a871ab29d..373c36d00 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -3,6 +3,7 @@ Buffer = require 'buffer' Point = require 'point' Cursor = require 'cursor' Selection = require 'selection' +Range = require 'range' $ = require 'jquery' $$ = require 'template/builder' _ = require 'underscore' @@ -41,8 +42,8 @@ class Editor extends Template 'shift-up': 'select-up' 'shift-down': 'select-down' enter: 'newline' - backspace: 'backspace' - delete: 'delete' + backspace: 'delete-left' + delete: 'delete-right' 'meta-c': 'copy' @on 'move-right', => @moveCursorRight() @@ -54,8 +55,8 @@ class Editor extends Template @on 'select-up', => @selectUp() @on 'select-down', => @selectDown() @on 'newline', => @insertNewline() - @on 'backspace', => @backspace() - @on 'delete', => @delete() + @on 'delete-left', => @deleteLeft() + @on 'delete-right', => @deleteRight() @on 'copy', => @copySelection() @@ -191,11 +192,18 @@ class Editor extends Template selectLeft: -> @selection.selectLeft() selectUp: -> @selection.selectUp() selectDown: -> @selection.selectDown() - selectToPosition: (position) -> @selection.selectToPosition(position) + selectToPosition: (position) -> + @selection.selectToPosition(position) insertText: (text) -> @selection.insertText(text) insertNewline: -> @selection.insertNewline() - backspace: -> @selection.backspace() - delete: -> @selection.delete() copySelection: -> @selection.copy() + deleteLeft: -> + @selectLeft() if @selection.isEmpty() + @selection.delete() + + deleteRight: -> + @selectRight() if @selection.isEmpty() + @selection.delete() + From a3332571a997d25706854eaa54200c35fae6194e Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 1 Feb 2012 15:57:37 -0800 Subject: [PATCH 04/12] Remove backspace and change implementation of delete. A selection will only delete what is selected. Otherwise it will leave the buffer unmodified. --- spec/atom/selection-spec.coffee | 24 ++++++++++++++++++++++++ src/atom/selection.coffee | 24 +----------------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/spec/atom/selection-spec.coffee b/spec/atom/selection-spec.coffee index 0e8e59bc7..9effd1a7e 100644 --- a/spec/atom/selection-spec.coffee +++ b/spec/atom/selection-spec.coffee @@ -19,6 +19,30 @@ describe "Selection", -> expect(selection.anchor.getPosition()).toEqual range.start expect(selection.cursor.getPosition()).toEqual range.end + describe ".delete()", -> + describe "when nothing is selected", -> + it "deletes nothing", -> + selection.setRange new Range([0,3], [0,3]) + selection.delete() + expect(editor.buffer.getLine(0)).toBe "var quicksort = function () {" + + describe "when one line is selected", -> + it "deletes selected text", -> + selection.setRange new Range([0,4], [0,14]) + selection.delete() + expect(editor.buffer.getLine(0)).toBe "var = function () {" + + endOfLine = editor.buffer.getLine(0).length + selection.setRange new Range([0,0], [0, endOfLine]) + selection.delete() + expect(editor.buffer.getLine(0)).toBe "" + + describe "when multiple lines are selected", -> + it "deletes selected text", -> + selection.setRange new Range([0,1], [2,39]) + selection.delete() + expect(editor.buffer.getLine(0)).toBe "v;" + describe ".updateAppearence()", -> [charWidth, lineHeight] = [] diff --git a/src/atom/selection.coffee b/src/atom/selection.coffee index ab7db1dd2..d34b85e05 100644 --- a/src/atom/selection.coffee +++ b/src/atom/selection.coffee @@ -81,31 +81,9 @@ class Selection extends Template insertNewline: -> @insertText('\n') - backspace: -> - range = @getRange() - - if range.isEmpty() - if range.start.column == 0 - return if range.start.row == 0 - range.start.column = @editor.buffer.getLine(range.start.row - 1).length - range.start.row-- - else - range.start.column-- - - @editor.buffer.change(range, '') - delete: -> range = @getRange() - - if range.isEmpty() - if range.end.column == @editor.buffer.getLine(range.end.row).length - return if range.end.row == @editor.buffer.numLines() - 1 - range.end.column = 0 - range.end.row++ - else - range.end.column++ - - @editor.buffer.change(range, '') + @editor.buffer.change(range, '') unless range.isEmpty() isEmpty: -> @getRange().isEmpty() From d250580d1ba7a876e42b89af7c2bc0d0b85f2535 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 1 Feb 2012 16:04:19 -0800 Subject: [PATCH 05/12] Add getSelection method. Make getCursor return editors cursor var. --- src/atom/editor.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 373c36d00..9006f4555 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -175,8 +175,10 @@ class Editor extends Template else @scrollLeft() + @width() + getCursor: -> @cursor + getSelection: -> @selection + getCurrentLine: -> @buffer.getLine(@getCursorRow()) - getCursor: -> @selection.cursor moveCursorUp: -> @cursor.moveUp() moveCursorDown: -> @cursor.moveDown() moveCursorRight: -> @cursor.moveRight() From 09dd7fe91394547a2479ce91e95351dcaace0d3d Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 1 Feb 2012 16:08:03 -0800 Subject: [PATCH 06/12] :lipstick: --- src/atom/buffer.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/atom/buffer.coffee b/src/atom/buffer.coffee index d06cf0994..5bc3ebcdc 100644 --- a/src/atom/buffer.coffee +++ b/src/atom/buffer.coffee @@ -32,8 +32,8 @@ class Buffer getLines: -> @lines - getLine: (n) -> - @lines[n] + getLine: (row) -> + @lines[row] change: (preRange, string) -> @remove(preRange) From 14d4d5029eab4882d0ece23f5cc61b0a2d855906 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 1 Feb 2012 16:45:28 -0800 Subject: [PATCH 07/12] Rename DeleteChar to DeleteRight --- src/atom/vim-mode.coffee | 5 ++--- src/atom/vim-mode/commands.coffee | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/atom/vim-mode.coffee b/src/atom/vim-mode.coffee index a207980f6..054d15542 100644 --- a/src/atom/vim-mode.coffee +++ b/src/atom/vim-mode.coffee @@ -28,7 +28,7 @@ class VimMode @bindCommandModeKeys 'i': 'insert' 'd': 'delete' - 'x': 'delete-char' + 'x': 'delete-right' 'h': 'move-left' 'j': 'move-down' 'k': 'move-up' @@ -38,7 +38,7 @@ class VimMode @handleCommands 'insert': => @activateInsertMode() 'delete': => @delete() - 'delete-char': => new commands.DeleteChar(@editor) + 'delete-right': => new commands.DeleteRight(@editor) 'move-left': => new motions.MoveLeft(@editor) 'move-up': => new motions.MoveUp(@editor) 'move-down': => new motions.MoveDown @editor @@ -101,4 +101,3 @@ class VimMode topOperator: -> _.last @opStack - diff --git a/src/atom/vim-mode/commands.coffee b/src/atom/vim-mode/commands.coffee index 7a01e3e6b..6d110cd47 100644 --- a/src/atom/vim-mode/commands.coffee +++ b/src/atom/vim-mode/commands.coffee @@ -2,9 +2,9 @@ class Command constructor: (@editor) -> isComplete: -> true -class DeleteChar extends Command +class DeleteRight extends Command execute: -> - @editor.deleteChar() + @editor.deleteRight() -module.exports = { DeleteChar } +module.exports = { DeleteRight } From d45c6f9926d6c59685b233f6d8eeb54dc1e5e399 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 1 Feb 2012 16:46:05 -0800 Subject: [PATCH 08/12] Delete is on selection now, not editor --- src/atom/vim-mode/operators.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom/vim-mode/operators.coffee b/src/atom/vim-mode/operators.coffee index 080e194b4..6d06b8ab9 100644 --- a/src/atom/vim-mode/operators.coffee +++ b/src/atom/vim-mode/operators.coffee @@ -37,7 +37,7 @@ class Delete execute: -> if @motion @motion.select() - @editor.delete() + @editor.getSelection().delete() else @editor.deleteLine() From 0f75561a1295bee219117c51ec8044014a9b3be8 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 1 Feb 2012 16:48:55 -0800 Subject: [PATCH 09/12] Use renamed cursor methods on editor --- src/atom/vim-mode/motions.coffee | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/atom/vim-mode/motions.coffee b/src/atom/vim-mode/motions.coffee index 86d24abc5..d11aff2af 100644 --- a/src/atom/vim-mode/motions.coffee +++ b/src/atom/vim-mode/motions.coffee @@ -6,41 +6,41 @@ class Motion class MoveLeft extends Motion execute: -> - {column, row} = @editor.getPosition() - @editor.moveLeft() if column > 0 + {column, row} = @editor.getCursorPosition() + @editor.moveCursorLeft() if column > 0 select: -> - position = @editor.getPosition() + position = @editor.getCursorPosition() position.column-- if position.column > 0 @editor.selectToPosition position class MoveRight extends Motion execute: -> - {column, row} = @editor.getPosition() - currentLineLength = @editor.getLineText(row).length - @editor.moveRight() if column < currentLineLength + {column, row} = @editor.getCursorPosition() + currentLineLength = @editor.buffer.getLine(row).length + @editor.moveCursorRight() if column < currentLineLength class MoveUp extends Motion execute: -> - {column, row} = @editor.getPosition() - @editor.moveUp() if row > 0 + {column, row} = @editor.getCursorPosition() + @editor.moveCursorUp() if row > 0 class MoveDown extends Motion execute: -> - {column, row} = @editor.getPosition() - @editor.moveDown() if row < (@editor.getAceSession().getLength() - 1) + {column, row} = @editor.getCursorPosition() + @editor.moveCursorDown() if row < (@editor.buffer.numLines() - 1) class MoveToNextWord extends Motion execute: -> - @editor.setPosition(@nextWordPosition()) + @editor.setCursorPosition(@nextWordPosition()) select: -> @editor.selectToPosition(@nextWordPosition()) nextWordPosition: -> regex = getWordRegex() - { row, column } = @editor.getPosition() - rightOfCursor = @editor.getLineText(row).substring(column) + { row, column } = @editor.getCursorPosition() + rightOfCursor = @editor.buffer.getLine(row).substring(column) match = regex.exec(rightOfCursor) # If we're on top of part of a word, match the next one. @@ -62,8 +62,7 @@ class SelectLines extends Motion setCount: (@count) -> select: -> - @editor.setPosition(column: 0, row: @editor.getRow()) - @editor.selectToPosition(column: 0, row: @editor.getRow() + @count) + @editor.setCursorPosition(column: 0, row: @editor.getCursorRow()) + @editor.selectToPosition(column: 0, row: @editor.getCursorRow() + @count) module.exports = { MoveLeft, MoveRight, MoveUp, MoveDown, MoveToNextWord, SelectLines } - From a8380a6df6b30d244d1969f56f4c3a021534780f Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 1 Feb 2012 16:49:46 -0800 Subject: [PATCH 10/12] Next word on at end of file moves cursor to end of file. Not sure how this was working before? Maybe a quirk of ace? --- src/atom/vim-mode/motions.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/atom/vim-mode/motions.coffee b/src/atom/vim-mode/motions.coffee index d11aff2af..7f7a2575a 100644 --- a/src/atom/vim-mode/motions.coffee +++ b/src/atom/vim-mode/motions.coffee @@ -48,8 +48,10 @@ class MoveToNextWord extends Motion if match column += match.index + else if row + 1 == @editor.buffer.numLines() + column = @editor.buffer.getLine(row).length else - nextLineMatch = regex.exec(@editor.getLineText(++row)) + nextLineMatch = regex.exec(@editor.buffer.getLine(++row)) column = nextLineMatch?.index or 0 { row, column } From 53d68d93ca2355be8526007edcc57b2586d6eb5a Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 1 Feb 2012 16:50:10 -0800 Subject: [PATCH 11/12] Rename old methods and make the specs pass! --- spec/atom/vim-mode-spec.coffee | 67 +++++++++++++++++----------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/spec/atom/vim-mode-spec.coffee b/spec/atom/vim-mode-spec.coffee index 4f53aaef6..66359b934 100644 --- a/spec/atom/vim-mode-spec.coffee +++ b/spec/atom/vim-mode-spec.coffee @@ -1,7 +1,7 @@ Editor = require 'editor' VimMode = require 'vim-mode' -xdescribe "VimMode", -> +describe "VimMode", -> editor = null beforeEach -> @@ -38,136 +38,137 @@ xdescribe "VimMode", -> describe "the x keybinding", -> it "deletes a charachter", -> editor.buffer.setText("12345") - editor.setPosition(column: 1, row: 0) + editor.setCursorPosition([0, 1]) editor.trigger keydownEvent('x') expect(editor.buffer.getText()).toBe '1345' - expect(editor.getPosition()).toEqual(column: 1, row: 0) + expect(editor.getCursorPosition()).toEqual([0, 1]) describe "the d keybinding", -> describe "when followed by a d", -> it "deletes the current line", -> editor.buffer.setText("12345\nabcde\nABCDE") - editor.setPosition(column: 1, row: 1) + editor.setCursorPosition([1,1]) editor.trigger keydownEvent('d') editor.trigger keydownEvent('d') expect(editor.buffer.getText()).toBe "12345\nABCDE" - expect(editor.getPosition()).toEqual(column: 0, row: 1) + expect(editor.getCursorPosition()).toEqual([1,0]) describe "when the second d is prefixed by a count", -> it "deletes n lines, starting from the current", -> editor.buffer.setText("12345\nabcde\nABCDE\nQWERT") - editor.setPosition(column: 1, row: 1) + editor.setCursorPosition([1,1]) editor.trigger keydownEvent('d') editor.trigger keydownEvent('2') editor.trigger keydownEvent('d') expect(editor.buffer.getText()).toBe "12345\nQWERT" - expect(editor.getPosition()).toEqual(column: 0, row: 1) + expect(editor.getCursorPosition()).toEqual([1,0]) describe "when followed by an h", -> it "deletes the previous letter on the current line", -> editor.buffer.setText("abcd\n01234") - editor.setPosition(column: 1, row: 1) + editor.setCursorPosition([1,1]) editor.trigger keydownEvent 'd' editor.trigger keydownEvent 'h' expect(editor.buffer.getText()).toBe "abcd\n1234" - expect(editor.getPosition()).toEqual {column: 0, row: 1} + expect(editor.getCursorPosition()).toEqual([1,0]) editor.trigger keydownEvent 'd' editor.trigger keydownEvent 'h' expect(editor.buffer.getText()).toBe "abcd\n1234" - expect(editor.getPosition()).toEqual {column: 0, row: 1} + expect(editor.getCursorPosition()).toEqual([1,0]) describe "when followed by a w", -> it "deletes to the beginning of the next word", -> editor.buffer.setText("abcd efg") - editor.setPosition(column: 2, row: 0) + editor.setCursorPosition([0,2]) editor.trigger keydownEvent('d') editor.trigger keydownEvent('w') expect(editor.buffer.getText()).toBe "abefg" - expect(editor.getPosition()).toEqual {column: 2, row: 0} + expect(editor.getCursorPosition()).toEqual([0,2]) editor.buffer.setText("one two three four") - editor.setPosition(column: 0, row: 0) + editor.setCursorPosition([0,0]) editor.trigger keydownEvent('d') editor.trigger keydownEvent('3') editor.trigger keydownEvent('w') expect(editor.buffer.getText()).toBe "four" - expect(editor.getPosition()).toEqual {column: 0, row: 0} + expect(editor.getCursorPosition()).toEqual([0,0]) describe "basic motion bindings", -> beforeEach -> editor.buffer.setText("12345\nabcde\nABCDE") - editor.setPosition(column: 1, row: 1) + editor.setCursorPosition([1,1]) describe "the h keybinding", -> it "moves the cursor left, but not to the previous line", -> editor.trigger keydownEvent('h') - expect(editor.getPosition()).toEqual(column: 0, row: 1) + expect(editor.getCursorPosition()).toEqual([1,0]) editor.trigger keydownEvent('h') - expect(editor.getPosition()).toEqual(column: 0, row: 1) + expect(editor.getCursorPosition()).toEqual([1,0]) describe "the j keybinding", -> it "moves the cursor down, but not to the end of the last line", -> editor.trigger keydownEvent 'j' - expect(editor.getPosition()).toEqual(column: 1, row: 2) + expect(editor.getCursorPosition()).toEqual([2,1]) editor.trigger keydownEvent 'j' - expect(editor.getPosition()).toEqual(column: 1, row: 2) + expect(editor.getCursorPosition()).toEqual([2,1]) describe "the k keybinding", -> it "moves the cursor up, but not to the beginning of the first line", -> editor.trigger keydownEvent('k') - expect(editor.getPosition()).toEqual(column: 1, row: 0) + expect(editor.getCursorPosition()).toEqual([0,1]) editor.trigger keydownEvent('k') - expect(editor.getPosition()).toEqual(column: 1, row: 0) + expect(editor.getCursorPosition()).toEqual([0,1]) describe "the l keybinding", -> it "moves the cursor right, but not to the next line", -> - editor.setPosition(column: 4, row: 1) + editor.setCursorPosition([1,4]) editor.trigger keydownEvent('l') - expect(editor.getPosition()).toEqual(column: 5, row: 1) + expect(editor.getCursorPosition()).toEqual([1,5]) editor.trigger keydownEvent('l') - expect(editor.getPosition()).toEqual(column: 5, row: 1) + expect(editor.getCursorPosition()).toEqual([1,5]) describe "the w keybinding", -> it "moves the cursor to the beginning of the next word", -> editor.buffer.setText("ab cde1+- \n xyz\n\nzip") - editor.setPosition(column: 0, row: 0) + editor.setCursorPosition([0,0]) editor.trigger keydownEvent('w') - expect(editor.getPosition()).toEqual(column: 3, row: 0) + expect(editor.getCursorPosition()).toEqual([0,3]) editor.trigger keydownEvent('w') - expect(editor.getPosition()).toEqual(column: 7, row: 0) + expect(editor.getCursorPosition()).toEqual([0,7]) editor.trigger keydownEvent('w') - expect(editor.getPosition()).toEqual(column: 1, row: 1) + expect(editor.getCursorPosition()).toEqual([1,1]) editor.trigger keydownEvent('w') - expect(editor.getPosition()).toEqual(column: 0, row: 2) + expect(editor.getCursorPosition()).toEqual([2,0]) editor.trigger keydownEvent('w') - expect(editor.getPosition()).toEqual(column: 0, row: 3) + expect(editor.getCursorPosition()).toEqual([3,0]) + editor.setCursorPosition [3,0] editor.trigger keydownEvent('w') - expect(editor.getPosition()).toEqual(column: 3, row: 3) + expect(editor.getCursorPosition()).toEqual([3,3]) describe "numeric prefix bindings", -> it "repeats the following operation N times", -> editor.buffer.setText("12345") - editor.setPosition(column: 1, row: 0) + editor.setCursorPosition([0,1]) editor.trigger keydownEvent('3') editor.trigger keydownEvent('x') @@ -175,7 +176,7 @@ xdescribe "VimMode", -> expect(editor.buffer.getText()).toBe '15' editor.buffer.setText("123456789abc") - editor.setPosition(column: 0, row: 0) + editor.setCursorPosition([0,0]) editor.trigger keydownEvent('1') editor.trigger keydownEvent('0') editor.trigger keydownEvent('x') From 1e6d4c618e1f7c44d89af8b9b7a2399c4bdb792a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 1 Feb 2012 20:19:26 -0700 Subject: [PATCH 12/12] Refactor Buffer.change --- src/atom/buffer.coffee | 60 +++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/src/atom/buffer.coffee b/src/atom/buffer.coffee index 5bc3ebcdc..b78b5fa40 100644 --- a/src/atom/buffer.coffee +++ b/src/atom/buffer.coffee @@ -1,4 +1,6 @@ +_ = require 'underscore' fs = require 'fs' +Range = require 'range' module.exports = class Buffer @@ -35,42 +37,34 @@ class Buffer getLine: (row) -> @lines[row] - change: (preRange, string) -> - @remove(preRange) - postRange = @insert(preRange.start, string) - @trigger 'change', { preRange, postRange, string } + change: (preRange, newText) -> + postRange = new Range(_.clone(preRange.start), _.clone(preRange.start)) + prefix = @lines[preRange.start.row][0...preRange.start.column] + suffix = @lines[preRange.end.row][preRange.end.column..] + newTextLines = newText.split('\n') - remove: (range) -> - prefix = @lines[range.start.row][0...range.start.column] - suffix = @lines[range.end.row][range.end.column..] - @lines[range.start.row..range.end.row] = prefix + suffix - - insert: ({row, column}, string) -> - postRange = - start: { row, column } - end: { row, column } - - prefix = @lines[row][0...column] - suffix = @lines[row][column..] - - lines = string.split('\n') - - if lines.length == 1 - @lines[row] = prefix + string + suffix - postRange.end.column += string.length + if newTextLines.length == 1 + postRange.end.column += newText.length + linesToInsert = [prefix + newText + suffix] else - for line, i in lines - curRow = row + i - if i == 0 # replace first line - @lines[curRow] = prefix + line - else if i < lines.length - 1 # insert middle lines - @lines[curRow...curRow] = line - else # insert last line - @lines[curRow...curRow] = line + suffix - postRange.end.row = curRow - postRange.end.column = line.length + firstLineIndex = 0 + lastLineIndex = newTextLines.length - 1 - postRange + linesToInsert = + for line, i in newTextLines + switch i + when firstLineIndex + prefix + line + when lastLineIndex + postRange.end.row += i + postRange.end.column = line.length + line + suffix + else + line + + @lines[preRange.start.row..preRange.end.row] = linesToInsert + + @trigger 'change', { preRange, postRange, string: newText } numLines: -> @getLines().length