Merge pull request #94 from github/atomic-soft-tabs

Atomic soft tabs
This commit is contained in:
Corey Johnson
2012-11-06 11:56:39 -08:00
18 changed files with 211 additions and 173 deletions

View File

@@ -145,21 +145,6 @@ describe "DisplayBuffer", ->
expect(event.lineNumbersChanged).toBeTruthy()
describe "structural folding", ->
describe "the foldable flag on screen lines", ->
it "sets 'foldable' to true for screen lines that start a foldable region", ->
expect(displayBuffer.lineForRow(0).foldable).toBeTruthy()
expect(displayBuffer.lineForRow(1).foldable).toBeTruthy()
expect(displayBuffer.lineForRow(2).foldable).toBeFalsy()
expect(displayBuffer.lineForRow(3).foldable).toBeFalsy()
describe "when a foldable line is wrapped", ->
it "only marks the first screen line as foldable", ->
displayBuffer.setSoftWrapColumn(20)
expect(displayBuffer.lineForRow(0).foldable).toBeTruthy()
expect(displayBuffer.lineForRow(1).foldable).toBeFalsy()
expect(displayBuffer.lineForRow(2).foldable).toBeTruthy()
expect(displayBuffer.lineForRow(3).foldable).toBeFalsy()
describe ".unfoldAll()", ->
it "unfolds every folded line", ->
displayBuffer.foldBufferRow(0)

View File

@@ -110,12 +110,12 @@ describe "EditSession", ->
lastLine = buffer.lineForRow(lastLineIndex)
expect(lastLine.length).toBeGreaterThan(0)
editSession.setCursorScreenPosition(row: lastLineIndex, column: 1)
editSession.setCursorScreenPosition(row: lastLineIndex, column: editSession.getTabLength())
editSession.moveCursorDown()
expect(editSession.getCursorScreenPosition()).toEqual(row: lastLineIndex, column: lastLine.length)
editSession.moveCursorUp()
expect(editSession.getCursorScreenPosition().column).toBe 1
expect(editSession.getCursorScreenPosition().column).toBe editSession.getTabLength()
it "retains a goal column of 0 when moving back up", ->
lastLineIndex = buffer.getLines().length - 1
@@ -138,9 +138,9 @@ describe "EditSession", ->
describe ".moveCursorLeft()", ->
it "moves the cursor by one column to the left", ->
editSession.setCursorScreenPosition([3, 3])
editSession.setCursorScreenPosition([1, 8])
editSession.moveCursorLeft()
expect(editSession.getCursorScreenPosition()).toEqual [3, 2]
expect(editSession.getCursorScreenPosition()).toEqual [1, 7]
describe "when the cursor is in the first column", ->
describe "when there is a previous line", ->
@@ -155,6 +155,13 @@ describe "EditSession", ->
editSession.moveCursorLeft()
expect(editSession.getCursorScreenPosition()).toEqual(row: 0, column: 0)
describe "when softTabs is enabled and the cursor is preceded by leading whitespace", ->
it "skips tabLength worth of whitespace at a time", ->
editSession.setCursorBufferPosition([5, 6])
editSession.moveCursorLeft()
expect(editSession.getCursorBufferPosition()).toEqual [5, 4]
it "merges cursors when they overlap", ->
editSession.setCursorScreenPosition([0, 0])
editSession.addCursorAtScreenPosition([0, 1])
@@ -354,14 +361,14 @@ describe "EditSession", ->
describe ".selectToScreenPosition(screenPosition)", ->
it "expands the last selection to the given position", ->
editSession.setSelectedBufferRange([[3, 0], [4, 5]])
editSession.addCursorAtScreenPosition([5, 5])
editSession.selectToScreenPosition([6, 1])
editSession.addCursorAtScreenPosition([5, 6])
editSession.selectToScreenPosition([6, 2])
selections = editSession.getSelections()
expect(selections.length).toBe 2
[selection1, selection2] = selections
expect(selection1.getScreenRange()).toEqual [[3, 0], [4, 5]]
expect(selection2.getScreenRange()).toEqual [[5, 5], [6, 1]]
expect(selection2.getScreenRange()).toEqual [[5, 6], [6, 2]]
it "merges selections if they intersect, maintaining the directionality of the last selection", ->
editSession.setCursorScreenPosition([4, 10])
@@ -1210,7 +1217,7 @@ describe "EditSession", ->
describe "when autoIndent is disabled", ->
describe "if 'softTabs' is true (the default)", ->
it "inserts 'tabLength' spaces into the buffer", ->
tabRegex = new RegExp("^[ ]{#{editSession.tabLength}}")
tabRegex = new RegExp("^[ ]{#{editSession.getTabLength()}}")
expect(buffer.lineForRow(0)).not.toMatch(tabRegex)
editSession.indent()
expect(buffer.lineForRow(0)).toMatch(tabRegex)
@@ -1225,10 +1232,9 @@ describe "EditSession", ->
describe "when autoIndent is enabled", ->
describe "when the cursor's column is less than the suggested level of indentation", ->
describe "when 'softTabs' is true (the default)", ->
it "inserts enough whitespace to bring the line to the suggested level of indentaion", ->
it "moves the cursor to the end of the leading whitespace and inserts enough whitespace to bring the line to the suggested level of indentaion", ->
buffer.insert([5, 0], " \n")
editSession.tabLength = 2
editSession.setCursorBufferPosition [5, 2]
editSession.setCursorBufferPosition [5, 0]
editSession.setAutoIndent(true)
editSession.indent()
expect(buffer.lineForRow(5)).toMatch /^\s+$/
@@ -1236,22 +1242,21 @@ describe "EditSession", ->
expect(editSession.getCursorBufferPosition()).toEqual [5, 6]
describe "when 'softTabs' is false", ->
it "inserts enough tabs to bring the line to the suggested level of indentaion", ->
it "moves the cursor to the end of the leading whitespace and inserts enough tabs to bring the line to the suggested level of indentaion", ->
convertToHardTabs(buffer)
editSession.softTabs = false
buffer.insert([5, 0], "\t\n")
editSession.setCursorBufferPosition [5, 1]
editSession.setCursorBufferPosition [5, 0]
editSession.setAutoIndent(true)
editSession.indent()
expect(buffer.lineForRow(5)).toMatch /^\t\t\t$/
expect(editSession.getCursorBufferPosition()).toEqual [5, 3]
describe "when the cursor's column is greater than the suggested level of indentation", ->
describe "when the line's indent level is greater than the suggested level of indentation", ->
describe "when 'softTabs' is true (the default)", ->
it "inserts 'tabLength' spaces into the buffer", ->
it "moves the cursor to the end of the leading whitespace and inserts 'tabLength' spaces into the buffer", ->
buffer.insert([7, 0], " \n")
editSession.tabLength = 2
editSession.setCursorBufferPosition [7, 6]
editSession.setCursorBufferPosition [7, 2]
editSession.setAutoIndent(true)
editSession.indent()
expect(buffer.lineForRow(7)).toMatch /^\s+$/
@@ -1259,11 +1264,11 @@ describe "EditSession", ->
expect(editSession.getCursorBufferPosition()).toEqual [7, 8]
describe "when 'softTabs' is false", ->
it "inserts \t into the buffer", ->
it "moves the cursor to the end of the leading whitespace and inserts \t into the buffer", ->
convertToHardTabs(buffer)
editSession.softTabs = false
buffer.insert([7, 0], "\t\t\t\n")
editSession.setCursorBufferPosition [7, 3]
editSession.setCursorBufferPosition [7, 1]
editSession.setAutoIndent(true)
editSession.indent()
expect(buffer.lineForRow(7)).toMatch /^\t\t\t\t$/
@@ -1284,12 +1289,12 @@ describe "EditSession", ->
editSession.indent()
expect(buffer.lineForRow(0)).toMatch(/^\t/)
expect(editSession.getCursorBufferPosition()).toEqual [0, 1]
expect(editSession.getCursorScreenPosition()).toEqual [0, editSession.tabLength]
expect(editSession.getCursorScreenPosition()).toEqual [0, editSession.getTabLength()]
editSession.indent()
expect(buffer.lineForRow(0)).toMatch(/^\t\t/)
expect(editSession.getCursorBufferPosition()).toEqual [0, 2]
expect(editSession.getCursorScreenPosition()).toEqual [0, editSession.tabLength * 2]
expect(editSession.getCursorScreenPosition()).toEqual [0, editSession.getTabLength() * 2]
describe "pasteboard operations", ->
pasteboard = null
@@ -1353,17 +1358,13 @@ describe "EditSession", ->
expect(editSession.lineForBufferRow(13)).toBe " }"
describe ".indentSelectedRows()", ->
beforeEach ->
editSession.tabLength = 2
describe "when nothing is selected", ->
describe "when softTabs is enabled", ->
it "indents line and retains selection", ->
editSession.tabLength = 2
editSession.setSelectedBufferRange([[0,3], [0,3]])
editSession.indentSelectedRows()
expect(buffer.lineForRow(0)).toBe " var quicksort = function () {"
expect(editSession.getSelectedBufferRange()).toEqual [[0, 3 + editSession.tabLength], [0, 3 + editSession.tabLength]]
expect(editSession.getSelectedBufferRange()).toEqual [[0, 3 + editSession.getTabLength()], [0, 3 + editSession.getTabLength()]]
describe "when softTabs is disabled", ->
it "indents line and retains selection", ->
@@ -1377,11 +1378,10 @@ describe "EditSession", ->
describe "when one line is selected", ->
describe "when softTabs is enabled", ->
it "indents line and retains selection", ->
editSession.tabLength = 2
editSession.setSelectedBufferRange([[0,4], [0,14]])
editSession.indentSelectedRows()
expect(buffer.lineForRow(0)).toBe "#{editSession.getTabText()}var quicksort = function () {"
expect(editSession.getSelectedBufferRange()).toEqual [[0, 4 + editSession.tabLength], [0, 14 + editSession.tabLength]]
expect(editSession.getSelectedBufferRange()).toEqual [[0, 4 + editSession.getTabLength()], [0, 14 + editSession.getTabLength()]]
describe "when softTabs is disabled", ->
it "indents line and retains selection", ->
@@ -1395,22 +1395,20 @@ describe "EditSession", ->
describe "when multiple lines are selected", ->
describe "when softTabs is enabled", ->
it "indents selected lines (that are not empty) and retains selection", ->
editSession.tabLength = 2
editSession.setSelectedBufferRange([[9,1], [11,15]])
editSession.indentSelectedRows()
expect(buffer.lineForRow(9)).toBe " };"
expect(buffer.lineForRow(10)).toBe ""
expect(buffer.lineForRow(11)).toBe " return sort(Array.apply(this, arguments));"
expect(editSession.getSelectedBufferRange()).toEqual [[9, 1 + editSession.tabLength], [11, 15 + editSession.tabLength]]
expect(editSession.getSelectedBufferRange()).toEqual [[9, 1 + editSession.getTabLength()], [11, 15 + editSession.getTabLength()]]
it "does not indent the last row if the selection ends at column 0", ->
editSession.tabLength = 2
editSession.setSelectedBufferRange([[9,1], [11,0]])
editSession.indentSelectedRows()
expect(buffer.lineForRow(9)).toBe " };"
expect(buffer.lineForRow(10)).toBe ""
expect(buffer.lineForRow(11)).toBe " return sort(Array.apply(this, arguments));"
expect(editSession.getSelectedBufferRange()).toEqual [[9, 1 + editSession.tabLength], [11, 0]]
expect(editSession.getSelectedBufferRange()).toEqual [[9, 1 + editSession.getTabLength()], [11, 0]]
describe "when softTabs is disabled", ->
it "indents selected lines (that are not empty) and retains selection", ->
@@ -1424,15 +1422,12 @@ describe "EditSession", ->
expect(editSession.getSelectedBufferRange()).toEqual [[9, 1 + 1], [11, 15 + 1]]
describe ".outdentSelectedRows()", ->
beforeEach ->
editSession.tabLength = 2
describe "when nothing is selected", ->
it "outdents line and retains selection", ->
editSession.setSelectedBufferRange([[1,3], [1,3]])
editSession.outdentSelectedRows()
expect(buffer.lineForRow(1)).toBe "var sort = function(items) {"
expect(editSession.getSelectedBufferRange()).toEqual [[1, 3 - editSession.tabLength], [1, 3 - editSession.tabLength]]
expect(editSession.getSelectedBufferRange()).toEqual [[1, 3 - editSession.getTabLength()], [1, 3 - editSession.getTabLength()]]
it "outdents when indent is less than a tab length", ->
editSession.insertText(' ')
@@ -1460,7 +1455,7 @@ describe "EditSession", ->
editSession.setSelectedBufferRange([[1,4], [1,14]])
editSession.outdentSelectedRows()
expect(buffer.lineForRow(1)).toBe "var sort = function(items) {"
expect(editSession.getSelectedBufferRange()).toEqual [[1, 4 - editSession.tabLength], [1, 14 - editSession.tabLength]]
expect(editSession.getSelectedBufferRange()).toEqual [[1, 4 - editSession.getTabLength()], [1, 14 - editSession.getTabLength()]]
describe "when multiple lines are selected", ->
it "outdents selected lines and retains editSession", ->
@@ -1470,7 +1465,7 @@ describe "EditSession", ->
expect(buffer.lineForRow(1)).toBe "var sort = function(items) {"
expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;"
expect(buffer.lineForRow(3)).toBe " var pivot = items.shift(), current, left = [], right = [];"
expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [3, 15 - editSession.tabLength]]
expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [3, 15 - editSession.getTabLength()]]
it "does not outdent the last line of the selection if it ends at column 0", ->
editSession.setSelectedBufferRange([[0,1], [3,0]])
@@ -1637,18 +1632,18 @@ describe "EditSession", ->
it "merges cursors when the change causes them to overlap", ->
editSession.setCursorScreenPosition([0, 0])
editSession.addCursorAtScreenPosition([0, 1])
editSession.addCursorAtScreenPosition([1, 1])
editSession.addCursorAtScreenPosition([0, 2])
editSession.addCursorAtScreenPosition([1, 2])
[cursor1, cursor2, cursor3] = editSession.getCursors()
expect(editSession.getCursors().length).toBe 3
buffer.delete([[0, 0], [0, 1]])
buffer.delete([[0, 0], [0, 2]])
expect(editSession.getCursors().length).toBe 2
expect(editSession.getCursors()).toEqual [cursor1, cursor3]
expect(cursor1.getBufferPosition()).toEqual [0,0]
expect(cursor3.getBufferPosition()).toEqual [1,1]
expect(cursor3.getBufferPosition()).toEqual [1,2]
describe "folding", ->
describe "structural folding", ->

View File

@@ -486,13 +486,13 @@ describe "Editor", ->
rootView.setFontSize(10)
lineHeightBefore = editor.lineHeight
charWidthBefore = editor.charWidth
editor.setCursorScreenPosition [5, 5]
editor.setCursorScreenPosition [5, 6]
rootView.setFontSize(30)
expect(editor.css('font-size')).toBe '30px'
expect(editor.lineHeight).toBeGreaterThan lineHeightBefore
expect(editor.charWidth).toBeGreaterThan charWidthBefore
expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 5 * editor.charWidth }
expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 6 * editor.charWidth }
# ensure we clean up font size subscription
editor.trigger('core:close')

View File

@@ -77,7 +77,7 @@ describe "RootView", ->
editor4 = editor2.splitDown()
editor2.edit(rootView.project.buildEditSessionForPath('dir/b'))
editor3.edit(rootView.project.buildEditSessionForPath('sample.js'))
editor3.setCursorScreenPosition([2, 3])
editor3.setCursorScreenPosition([2, 4])
editor4.edit(rootView.project.buildEditSessionForPath('sample.txt'))
editor4.setCursorScreenPosition([0, 2])
rootView.attachToDom()
@@ -98,7 +98,7 @@ describe "RootView", ->
expect(editor1.getPath()).toBe require.resolve('fixtures/dir/a')
expect(editor2.getPath()).toBe require.resolve('fixtures/dir/b')
expect(editor3.getPath()).toBe require.resolve('fixtures/sample.js')
expect(editor3.getCursorScreenPosition()).toEqual [2, 3]
expect(editor3.getCursorScreenPosition()).toEqual [2, 4]
expect(editor4.getPath()).toBe require.resolve('fixtures/sample.txt')
expect(editor4.getCursorScreenPosition()).toEqual [0, 2]

View File

@@ -35,4 +35,4 @@ describe "TextMateBundle", ->
it "uses plain text if no grammar can be found", ->
filePath = require.resolve("this-is-not-a-real-file")
expect(TextMateBundle.grammarForFilePath(filePath).name).toBe "Plain Text"
expect(TextMateBundle.grammarForFilePath(filePath).name).toBe "Plain Text"

View File

@@ -10,10 +10,10 @@ describe "TextMateGrammar", ->
beforeEach ->
grammar = TextMateBundle.grammarForFilePath("hello.coffee")
describe ".getLineTokens(line, currentRule)", ->
describe ".tokenizeLine(line, { ruleStack, tabLength })", ->
describe "when the entire line matches a single pattern with no capture groups", ->
it "returns a single token with the correct scope", ->
{tokens} = grammar.getLineTokens("return")
{tokens} = grammar.tokenizeLine("return")
expect(tokens.length).toBe 1
[token] = tokens
@@ -21,7 +21,7 @@ describe "TextMateGrammar", ->
describe "when the entire line matches a single pattern with capture groups", ->
it "returns a single token with the correct scope", ->
{tokens} = grammar.getLineTokens("new foo.bar.Baz")
{tokens} = grammar.tokenizeLine("new foo.bar.Baz")
expect(tokens.length).toBe 3
[newOperator, whitespace, className] = tokens
@@ -32,12 +32,12 @@ describe "TextMateGrammar", ->
describe "when the line doesn't match any patterns", ->
it "returns the entire line as a single simple token with the grammar's scope", ->
textGrammar = TextMateBundle.grammarForFilePath('foo.txt')
{tokens} = textGrammar.getLineTokens("abc def")
{tokens} = textGrammar.tokenizeLine("abc def")
expect(tokens.length).toBe 1
describe "when the line matches multiple patterns", ->
it "returns multiple tokens, filling in regions that don't match patterns with tokens in the grammar's global scope", ->
{tokens} = grammar.getLineTokens(" return new foo.bar.Baz ")
{tokens} = grammar.tokenizeLine(" return new foo.bar.Baz ")
expect(tokens.length).toBe 7
@@ -51,7 +51,7 @@ describe "TextMateGrammar", ->
describe "when the line matches a pattern with optional capture groups", ->
it "only returns tokens for capture groups that matched", ->
{tokens} = grammar.getLineTokens("class Quicksort")
{tokens} = grammar.tokenizeLine("class Quicksort")
expect(tokens.length).toBe 3
expect(tokens[0].value).toBe "class"
expect(tokens[1].value).toBe " "
@@ -59,7 +59,7 @@ describe "TextMateGrammar", ->
describe "when the line matches a rule with nested capture groups and lookahead capture groups beyond the scope of the overall match", ->
it "creates distinct tokens for nested captures and does not return tokens beyond the scope of the overall capture", ->
{tokens} = grammar.getLineTokens(" destroy: ->")
{tokens} = grammar.tokenizeLine(" destroy: ->")
expect(tokens.length).toBe 6
expect(tokens[0]).toEqual(value: ' ', scopes: ["source.coffee", "meta.function.coffee"])
expect(tokens[1]).toEqual(value: 'destro', scopes: ["source.coffee", "meta.function.coffee", "entity.name.function.coffee"])
@@ -71,13 +71,13 @@ describe "TextMateGrammar", ->
describe "when the line matches a pattern that includes a rule", ->
it "returns tokens based on the included rule", ->
{tokens} = grammar.getLineTokens("7777777")
{tokens} = grammar.tokenizeLine("7777777")
expect(tokens.length).toBe 1
expect(tokens[0]).toEqual value: '7777777', scopes: ['source.coffee', 'constant.numeric.coffee']
describe "when the line is an interpolated string", ->
it "returns the correct tokens", ->
{tokens} = grammar.getLineTokens('"the value is #{@x} my friend"')
{tokens} = grammar.tokenizeLine('"the value is #{@x} my friend"')
expect(tokens[0]).toEqual value: '"', scopes: ["source.coffee","string.quoted.double.coffee","punctuation.definition.string.begin.coffee"]
expect(tokens[1]).toEqual value: "the value is ", scopes: ["source.coffee","string.quoted.double.coffee"]
@@ -89,7 +89,7 @@ describe "TextMateGrammar", ->
describe "when the line has an interpolated string inside an interpolated string", ->
it "returns the correct tokens", ->
{tokens} = grammar.getLineTokens('"#{"#{@x}"}"')
{tokens} = grammar.tokenizeLine('"#{"#{@x}"}"')
expect(tokens[0]).toEqual value: '"', scopes: ["source.coffee","string.quoted.double.coffee","punctuation.definition.string.begin.coffee"]
expect(tokens[1]).toEqual value: '#{', scopes: ["source.coffee","string.quoted.double.coffee","source.coffee.embedded.source","punctuation.section.embedded.coffee"]
@@ -103,26 +103,26 @@ describe "TextMateGrammar", ->
describe "when the line is empty", ->
it "returns a single token which has the global scope", ->
{tokens} = grammar.getLineTokens('')
{tokens} = grammar.tokenizeLine('')
expect(tokens[0]).toEqual value: '', scopes: ["source.coffee"]
describe "when the line matches no patterns", ->
it "does not infinitely loop", ->
grammar = TextMateBundle.grammarForFilePath("sample.txt")
{tokens} = grammar.getLineTokens('hoo')
{tokens} = grammar.tokenizeLine('hoo')
expect(tokens.length).toBe 1
expect(tokens[0]).toEqual value: 'hoo', scopes: ["text.plain", "meta.paragraph.text"]
describe "when the line matches a pattern with a 'contentName'", ->
it "creates tokens using the content of contentName as the token name", ->
grammar = TextMateBundle.grammarForFilePath("sample.txt")
{tokens} = grammar.getLineTokens('ok, cool')
{tokens} = grammar.tokenizeLine('ok, cool')
expect(tokens[0]).toEqual value: 'ok, cool', scopes: ["text.plain", "meta.paragraph.text"]
describe "when the line matches a pattern with no `name` or `contentName`", ->
it "creates tokens without adding a new scope", ->
grammar = TextMateBundle.grammarsByFileType["rb"]
{tokens} = grammar.getLineTokens('%w|oh \\look|')
{tokens} = grammar.tokenizeLine('%w|oh \\look|')
expect(tokens.length).toBe 5
expect(tokens[0]).toEqual value: '%w|', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby", "punctuation.definition.string.begin.ruby"]
expect(tokens[1]).toEqual value: 'oh ', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby"]
@@ -131,7 +131,7 @@ describe "TextMateGrammar", ->
describe "when the line matches a begin/end pattern", ->
it "returns tokens based on the beginCaptures, endCaptures and the child scope", ->
{tokens} = grammar.getLineTokens("'''single-quoted heredoc'''")
{tokens} = grammar.tokenizeLine("'''single-quoted heredoc'''")
expect(tokens.length).toBe 3
@@ -140,9 +140,9 @@ describe "TextMateGrammar", ->
expect(tokens[2]).toEqual value: "'''", scopes: ['source.coffee', 'string.quoted.heredoc.coffee', 'punctuation.definition.string.end.coffee']
describe "when the pattern spans multiple lines", ->
it "uses the currentRule returned by the first line to parse the second line", ->
{tokens: firstTokens, stack} = grammar.getLineTokens("'''single-quoted")
{tokens: secondTokens, stack} = grammar.getLineTokens("heredoc'''", stack)
it "uses the ruleStack returned by the first line to parse the second line", ->
{tokens: firstTokens, ruleStack} = grammar.tokenizeLine("'''single-quoted")
{tokens: secondTokens, ruleStack} = grammar.tokenizeLine("heredoc'''", {ruleStack})
expect(firstTokens.length).toBe 2
expect(secondTokens.length).toBe 2
@@ -155,7 +155,7 @@ describe "TextMateGrammar", ->
describe "when the pattern contains sub-patterns", ->
it "returns tokens within the begin/end scope based on the sub-patterns", ->
{tokens} = grammar.getLineTokens('"""heredoc with character escape \\t"""')
{tokens} = grammar.tokenizeLine('"""heredoc with character escape \\t"""')
expect(tokens.length).toBe 4
@@ -167,7 +167,7 @@ describe "TextMateGrammar", ->
describe "when the end pattern contains a back reference", ->
it "constructs the end rule based on its back-references to captures in the begin rule", ->
grammar = TextMateBundle.grammarsByFileType["rb"]
{tokens} = grammar.getLineTokens('%w|oh|,')
{tokens} = grammar.tokenizeLine('%w|oh|,')
expect(tokens.length).toBe 4
expect(tokens[0]).toEqual value: '%w|', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby", "punctuation.definition.string.begin.ruby"]
expect(tokens[1]).toEqual value: 'oh', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby"]
@@ -176,7 +176,7 @@ describe "TextMateGrammar", ->
it "allows the rule containing that end pattern to be pushed to the stack multiple times", ->
grammar = TextMateBundle.grammarsByFileType["rb"]
{tokens} = grammar.getLineTokens('%Q+matz had some #{%Q-crazy ideas-} for ruby syntax+ # damn.')
{tokens} = grammar.tokenizeLine('%Q+matz had some #{%Q-crazy ideas-} for ruby syntax+ # damn.')
expect(tokens[0]).toEqual value: '%Q+', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","punctuation.definition.string.begin.ruby"]
expect(tokens[1]).toEqual value: 'matz had some ', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby"]
expect(tokens[2]).toEqual value: '#{', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","source.ruby.embedded.source","punctuation.section.embedded.ruby"]
@@ -193,7 +193,7 @@ describe "TextMateGrammar", ->
describe "when the pattern includes rules from another grammar", ->
it "parses tokens inside the begin/end patterns based on the included grammar's rules", ->
grammar = TextMateBundle.grammarsByFileType["html.erb"]
{tokens} = grammar.getLineTokens("<div class='name'><%= User.find(2).full_name %></div>")
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
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"]
@@ -232,9 +232,9 @@ describe "TextMateGrammar", ->
}
]
{tokens, stack} = grammar.getLineTokens("// a singleLineComment")
expect(stack.length).toBe 1
expect(stack[0].scopeName).toBe "source.imaginaryLanguage"
{tokens, ruleStack} = grammar.tokenizeLine("// a singleLineComment")
expect(ruleStack.length).toBe 1
expect(ruleStack[0].scopeName).toBe "source.imaginaryLanguage"
expect(tokens.length).toBe 2
expect(tokens[0].value).toBe "//"
@@ -242,5 +242,5 @@ describe "TextMateGrammar", ->
it "does not loop infinitley (regression)", ->
grammar = TextMateBundle.grammarForFilePath("hello.js")
{tokens, stack} = grammar.getLineTokens("// line comment")
{tokens, stack} = grammar.getLineTokens(" // second line comment with a single leading space", stack)
{tokens, ruleStack} = grammar.tokenizeLine("// line comment")
{tokens, ruleStack} = grammar.tokenizeLine(" // second line comment with a single leading space", ruleStack)

View File

@@ -9,7 +9,8 @@ describe "TokenizedBuffer", ->
beforeEach ->
editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false)
{ tokenizedBuffer, buffer } = editSession
buffer = editSession.buffer
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
afterEach ->
editSession.destroy()
@@ -49,7 +50,7 @@ describe "TokenizedBuffer", ->
expect(event.newRange).toEqual new Range([0, 0], [2,0])
# line 2 is unchanged
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.lineForScreenRow(2).tokens[2]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
it "updates tokens for lines beyond the changed lines if needed", ->
buffer.insert([5, 30], '/* */')
@@ -85,9 +86,9 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.lineForScreenRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
# lines below deleted regions should be shifted upward
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.lineForScreenRow(3).tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
expect(tokenizedBuffer.lineForScreenRow(4).tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js'])
expect(tokenizedBuffer.lineForScreenRow(2).tokens[2]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.lineForScreenRow(3).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
expect(tokenizedBuffer.lineForScreenRow(4).tokens[4]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js'])
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -126,7 +127,7 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.lineForScreenRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
# previous line 3 is pushed down to become line 5
expect(tokenizedBuffer.lineForScreenRow(5).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
expect(tokenizedBuffer.lineForScreenRow(5).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -157,16 +158,18 @@ describe "TokenizedBuffer", ->
beforeEach ->
tabLength = 2
editSession2 = fixturesProject.buildEditSessionForPath('sample-with-tabs.coffee', { tabLength })
{ buffer, tokenizedBuffer } = editSession2
buffer = editSession2.buffer
tokenizedBuffer = editSession2.displayBuffer.tokenizedBuffer
afterEach ->
editSession2.destroy()
it "always renders each tab as its own atomic token with a value of size tabLength", ->
tabAsSpaces = _.multiplyString(' ', editSession2.tabLength)
tabAsSpaces = _.multiplyString(' ', editSession2.getTabLength())
screenLine0 = tokenizedBuffer.lineForScreenRow(0)
expect(screenLine0.text).toBe "# Econ 101#{tabAsSpaces}"
{ tokens } = screenLine0
expect(tokens.length).toBe 3
expect(tokens[0].value).toBe "#"
expect(tokens[1].value).toBe " Econ 101"
@@ -175,3 +178,17 @@ describe "TokenizedBuffer", ->
expect(tokens[2].isAtomic).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{tabAsSpaces} buy()#{tabAsSpaces}while supply > demand"
describe ".setTabLength(tabLength)", ->
describe "when the file contains soft tabs", ->
it "retokenizes leading whitespace based on the new tab length", ->
expect(tokenizedBuffer.lineForScreenRow(5).tokens[0].isAtomic).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(5).tokens[0].value).toBe " "
expect(tokenizedBuffer.lineForScreenRow(5).tokens[1].isAtomic).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(5).tokens[1].value).toBe " "
tokenizedBuffer.setTabLength(4)
expect(tokenizedBuffer.lineForScreenRow(5).tokens[0].isAtomic).toBeTruthy()
expect(tokenizedBuffer.lineForScreenRow(5).tokens[0].value).toBe " "
expect(tokenizedBuffer.lineForScreenRow(5).tokens[1].isAtomic).toBeFalsy()
expect(tokenizedBuffer.lineForScreenRow(5).tokens[1].value).toBe " current "

View File

@@ -111,6 +111,15 @@ class Cursor
newPosition = [position.row, 0] if newPosition.isEqual(position)
@setBufferPosition(newPosition)
skipLeadingWhitespace: ->
position = @getBufferPosition()
range = @editSession.bufferRangeForBufferRow(position.row)
endOfLeadingWhitespace = null
@editSession.scanInRange /^[ \t]*/, range, (match, matchRange) =>
endOfLeadingWhitespace = matchRange.end
@setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position)
moveToEndOfLine: ->
@setBufferPosition([@getBufferRow(), Infinity])
@@ -160,7 +169,7 @@ class Cursor
getIndentLevel: ->
if @editSession.softTabs
@getBufferColumn() / @editSession.tabLength
@getBufferColumn() / @editSession.getTabLength()
else
@getBufferColumn()

View File

@@ -16,7 +16,6 @@ class DisplayBuffer
tokenizedBuffer: null
activeFolds: null
foldsById: null
lastTokenizedBufferChangeEvent: null
constructor: (@buffer, options={}) ->
@id = @constructor.idCounter++
@@ -26,8 +25,7 @@ class DisplayBuffer
@activeFolds = {}
@foldsById = {}
@buildLineMap()
@tokenizedBuffer.on 'change', (e) => @lastTokenizedBufferChangeEvent = e
@buffer.on "change.displayBuffer#{@id}", (e) => @handleBufferChange(e)
@tokenizedBuffer.on 'change', (e) => @handleTokenizedBufferChange(e)
buildLineMap: ->
@lineMap = new LineMap
@@ -177,8 +175,14 @@ class DisplayBuffer
bufferPositionForScreenPosition: (position, options) ->
@lineMap.bufferPositionForScreenPosition(position, options)
stateForScreenRow: (screenRow) ->
@tokenizedBuffer.stackForRow(screenRow)
scopesForBufferPosition: (bufferPosition) ->
@tokenizedBuffer.scopesForPosition(bufferPosition)
getTabLength: ->
@tokenizedBuffer.getTabLength()
setTabLength: (tabLength) ->
@tokenizedBuffer.setTabLength(tabLength)
clipScreenPosition: (position, options) ->
@lineMap.clipScreenPosition(position, options)
@@ -188,9 +192,9 @@ class DisplayBuffer
allFolds.push(folds...) for row, folds of @activeFolds
fold.handleBufferChange(e) for fold in allFolds
@handleTokenizedBufferChange(@lastTokenizedBufferChangeEvent)
handleTokenizedBufferChange: (e) ->
@handleBufferChange(e.bufferChange) if e.bufferChange
newRange = e.newRange.copy()
newRange.start.row = @bufferRowForScreenRow(@screenRowForBufferRow(newRange.start.row))
@@ -218,7 +222,6 @@ class DisplayBuffer
startBufferColumn = 0
while currentBufferRow <= endBufferRow
screenLine = @tokenizedBuffer.lineForScreenRow(currentBufferRow)
screenLine.foldable = @languageMode.doesBufferRowStartFold(currentBufferRow)
if fold = @largestFoldStartingAtBufferRow(currentBufferRow)
screenLine = screenLine.copy()
@@ -272,7 +275,6 @@ class DisplayBuffer
destroy: ->
@tokenizedBuffer.destroy()
@buffer.off ".displayBuffer#{@id}"
logLines: (start, end) ->
@lineMap.logLines(start, end)

View File

@@ -35,16 +35,14 @@ class EditSession
cursors: null
selections: null
autoIndent: false # TODO: re-enabled auto-indent after fixing the rest of tokenization
tabLength: null
softTabs: true
softWrap: false
constructor: ({@project, @buffer, @tabLength, @autoIndent, softTabs, @softWrap }) ->
constructor: ({@project, @buffer, tabLength, @autoIndent, softTabs, @softWrap }) ->
@id = @constructor.idCounter++
@softTabs = @buffer.usesSoftTabs() ? softTabs ? true
@languageMode = new LanguageMode(this, @buffer.getExtension())
@displayBuffer = new DisplayBuffer(@buffer, { @languageMode, @tabLength })
@tokenizedBuffer = @displayBuffer.tokenizedBuffer
@displayBuffer = new DisplayBuffer(@buffer, { @languageMode, tabLength })
@anchors = []
@anchorRanges = []
@cursors = []
@@ -108,7 +106,9 @@ class EditSession
setSoftWrap: (@softWrap) ->
getTabText: -> @buildIndentString(1)
getTabLength: -> @tabLength
getTabLength: -> @displayBuffer.getTabLength()
setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength)
clipBufferPosition: (bufferPosition) ->
@buffer.clipPosition(bufferPosition)
@@ -126,11 +126,11 @@ class EditSession
if line.match(/^\t/)
line.match(/^\t*/)?[0].length
else
line.match(/^\s*/)?[0].length / @tabLength
line.match(/^\s*/)?[0].length / @getTabLength()
buildIndentString: (number) ->
if @softTabs
_.multiplyString(" ", number * @tabLength)
_.multiplyString(" ", number * @getTabLength())
else
_.multiplyString("\t", number)
@@ -152,11 +152,11 @@ class EditSession
clipScreenPosition: (screenPosition, options) -> @displayBuffer.clipScreenPosition(screenPosition, options)
lineForScreenRow: (row) -> @displayBuffer.lineForRow(row)
linesForScreenRows: (start, end) -> @displayBuffer.linesForRows(start, end)
stateForScreenRow: (screenRow) -> @displayBuffer.stateForScreenRow(screenRow)
screenLineCount: -> @displayBuffer.lineCount()
maxScreenLineLength: -> @displayBuffer.maxLineLength()
getLastScreenRow: -> @displayBuffer.getLastRow()
bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow)
scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition)
logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)
insertText: (text, options) ->

View File

@@ -250,10 +250,9 @@ class Editor extends View
screenRangeForBufferRange: (range) -> @activeEditSession.screenRangeForBufferRange(range)
bufferRangeForScreenRange: (range) -> @activeEditSession.bufferRangeForScreenRange(range)
bufferRowsForScreenRows: (startRow, endRow) -> @activeEditSession.bufferRowsForScreenRows(startRow, endRow)
stateForScreenRow: (row) -> @activeEditSession.stateForScreenRow(row)
logCursorScope: ->
console.log @activeEditSession.tokenizedBuffer.scopesForPosition(@getCursorBufferPosition())
console.log @activeEditSession.scopesForBufferPosition(@getCursorBufferPosition())
pageDown: ->
newScrollTop = @scrollTop() + @scrollView[0].clientHeight

View File

@@ -48,9 +48,6 @@ class LanguageMode
@bracketAnchorRanges.push @editSession.addAnchorRange(range)
false
getTokenizedBuffer: ->
@editSession.tokenizedBuffer
isQuote: (string) ->
/'|"/.test(string)
@@ -69,7 +66,7 @@ class LanguageMode
@invertedPairedCharacters
toggleLineCommentsForBufferRows: (start, end) ->
scopes = @getTokenizedBuffer().scopesForPosition([start, 0])
scopes = @editSession.scopesForBufferPosition([start, 0])
return unless commentString = TextMateBundle.lineCommentStringForScope(scopes[0])
commentRegexString = _.escapeRegExp(commentString)
@@ -96,7 +93,7 @@ class LanguageMode
return null unless @doesBufferRowStartFold(bufferRow)
startIndentLevel = @editSession.indentationForBufferRow(bufferRow)
scopes = @getTokenizedBuffer().scopesForPosition([bufferRow, 0])
scopes = @editSession.scopesForBufferPosition([bufferRow, 0])
for row in [(bufferRow + 1)..@editSession.getLastBufferRow()]
continue if @editSession.isBufferRowBlank(row)
indentation = @editSession.indentationForBufferRow(row)
@@ -111,7 +108,7 @@ class LanguageMode
suggestedIndentForBufferRow: (bufferRow) ->
currentIndentLevel = @editSession.indentationForBufferRow(bufferRow)
scopes = @getTokenizedBuffer().scopesForPosition([bufferRow, 0])
scopes = @editSession.scopesForBufferPosition([bufferRow, 0])
return currentIndentLevel unless increaseIndentPattern = TextMateBundle.indentRegexForScope(scopes[0])
currentLine = @buffer.lineForRow(bufferRow)
@@ -140,7 +137,7 @@ class LanguageMode
return unless precedingRow?
precedingLine = @editSession.lineForBufferRow(precedingRow)
scopes = @getTokenizedBuffer().scopesForPosition([precedingRow, Infinity])
scopes = @editSession.scopesForBufferPosition([precedingRow, Infinity])
increaseIndentPattern = TextMateBundle.indentRegexForScope(scopes[0])
return unless increaseIndentPattern
@@ -151,7 +148,7 @@ class LanguageMode
@editSession.setIndentationForBufferRow(bufferRow, desiredIndentLevel)
autoDecreaseIndentForBufferRow: (bufferRow) ->
scopes = @getTokenizedBuffer().scopesForPosition([bufferRow, 0])
scopes = @editSession.scopesForBufferPosition([bufferRow, 0])
increaseIndentPattern = TextMateBundle.indentRegexForScope(scopes[0])
decreaseIndentPattern = TextMateBundle.outdentRegexForScope(scopes[0])
return unless increaseIndentPattern and decreaseIndentPattern
@@ -168,6 +165,5 @@ class LanguageMode
if desiredIndentLevel < currentIndentLevel
@editSession.setIndentationForBufferRow(bufferRow, desiredIndentLevel)
getLineTokens: (line, stack) ->
{tokens, stack} = @grammar.getLineTokens(line, stack)
tokenizeLine: (line, stack) ->
{tokens, stack} = @grammar.tokenizeLine(line, stack)

View File

@@ -2,14 +2,13 @@ _ = require 'underscore'
module.exports =
class ScreenLine
constructor: ({@tokens, @stack, @bufferRows, @startBufferColumn, @fold, @foldable}) ->
constructor: ({@tokens, @ruleStack, @bufferRows, @startBufferColumn, @fold}) ->
@bufferRows ?= 1
@startBufferColumn ?= 0
@foldable ?= false
@text = _.pluck(@tokens, 'value').join('')
copy: ->
new ScreenLine({@tokens, @stack, @bufferRows, @startBufferColumn, @fold, @foldable})
new ScreenLine({@tokens, @ruleStack, @bufferRows, @startBufferColumn, @fold})
clipScreenColumn: (column, options={}) ->
{ skipAtomicTokens } = options
@@ -73,13 +72,12 @@ class ScreenLine
tokens: leftTokens
bufferRows: 0
startBufferColumn: @startBufferColumn
stack: @stack
foldable: @foldable
ruleStack: @ruleStack
)
rightFragment = new ScreenLine(
tokens: rightTokens
startBufferColumn: @startBufferColumn + column
stack: @stack
ruleStack: @ruleStack
)
[leftFragment, rightFragment]

View File

@@ -187,6 +187,7 @@ class Selection
{ row, column } = @cursor.getBufferPosition()
if @isEmpty()
@cursor.skipLeadingWhitespace()
desiredIndent = @editSession.suggestedIndentForBufferRow(row)
delta = desiredIndent - @cursor.getIndentLevel()

View File

@@ -1,6 +1,7 @@
_ = require 'underscore'
fs = require 'fs'
plist = require 'plist'
Token = require 'token'
module.exports =
class TextMateGrammar
@@ -27,7 +28,8 @@ class TextMateGrammar
for name, data of repository
@repository[name] = new Rule(this, data)
getLineTokens: (line, ruleStack=[@initialRule]) ->
tokenizeLine: (line, {ruleStack, tabLength}={}) ->
ruleStack ?= [@initialRule]
ruleStack = new Array(ruleStack...) # clone ruleStack
tokens = []
position = 0
@@ -36,28 +38,38 @@ class TextMateGrammar
scopes = scopesFromStack(ruleStack)
if line.length == 0
tokens = [{value: "", scopes: scopes}]
return { tokens, scopes }
tokens = [new Token(value: "", scopes: scopes)]
return { tokens, ruleStack }
break if position == line.length
if match = _.last(ruleStack).getNextTokens(ruleStack, line, position)
{ nextTokens, tokensStartPosition, tokensEndPosition } = match
if position < tokensStartPosition # unmatched text before next tokens
tokens.push
tokens.push(new Token(
value: line[position...tokensStartPosition]
scopes: scopes
))
tokens.push(nextTokens...)
position = tokensEndPosition
else # push filler token for unmatched text at end of line
tokens.push
tokens.push(new Token(
value: line[position...line.length]
scopes: scopes
))
break
{ tokens, stack: ruleStack }
{ tokens: @breakOutAtomicTokens(tokens, tabLength), ruleStack }
breakOutAtomicTokens: (inputTokens, tabLength) ->
outputTokens = []
breakOutLeadingWhitespace = true
for token in inputTokens
outputTokens.push(token.breakOutAtomicTokens(tabLength, breakOutLeadingWhitespace)...)
breakOutLeadingWhitespace = token.isOnlyWhitespace() if breakOutLeadingWhitespace
outputTokens
ruleForInclude: (name) ->
if name[0] == "#"
@@ -158,7 +170,6 @@ class Pattern
getIncludedPatterns: (included) ->
if @include
rule = @grammar.ruleForInclude(@include)
# console.log "Could not find rule for include #{@include} in #{@grammar.name} grammar" unless rule
rule?.getIncludedPatterns(included) ? []
else
[this]
@@ -175,7 +186,7 @@ class Pattern
if zeroLengthMatch
tokens = []
else
tokens = [{ value: line[start...end], scopes: scopes }]
tokens = [new Token(value: line[start...end], scopes: scopes)]
if @pushRule
stack.push(@pushRule.getRuleToPush(line, captureIndices))
else if @popRule
@@ -201,18 +212,20 @@ class Pattern
continue
if childCaptureStart > previousChildCaptureEnd
tokens.push
tokens.push(new Token(
value: line[previousChildCaptureEnd...childCaptureStart]
scopes: scopes
))
captureTokens = @getTokensForCaptureIndices(line, captureIndices, scopes)
tokens.push(captureTokens...)
previousChildCaptureEnd = childCaptureEnd
if parentCaptureEnd > previousChildCaptureEnd
tokens.push
tokens.push(new Token(
value: line[previousChildCaptureEnd...parentCaptureEnd]
scopes: scopes
))
tokens

View File

@@ -5,9 +5,9 @@ class Token
value: null
scopes: null
isAtomic: null
isTab: null
isHardTab: null
constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @fold, @isTab}) ->
constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @isHardTab}) ->
@screenDelta = @value.length
@bufferDelta ?= @screenDelta
@@ -22,24 +22,46 @@ class Token
value2 = @value.substring(splitIndex)
[new Token(value: value1, scopes: @scopes), new Token(value: value2, scopes: @scopes)]
breakOutTabCharacters: (tabLength, showInvisibles) ->
return [this] unless /\t/.test(@value)
breakOutAtomicTokens: (tabLength, breakOutLeadingWhitespace) ->
if breakOutLeadingWhitespace
return [this] unless /^[ ]|\t/.test(@value)
else
return [this] unless /\t/.test(@value)
for substring in @value.match(/[^\t]+|\t/g)
if substring == "\t"
@buildTabToken(tabLength)
outputTokens = []
regex = new RegExp("([ ]{#{tabLength}})|(\t)|([^\t]+)", "g")
while match = regex.exec(@value)
[fullMatch, softTab, hardTab] = match
if softTab and breakOutLeadingWhitespace
outputTokens.push(@buildSoftTabToken(tabLength, false))
else if hardTab
breakOutLeadingWhitespace = false
outputTokens.push(@buildHardTabToken(tabLength, true))
else
new Token(value: substring, scopes: @scopes)
breakOutLeadingWhitespace = false
outputTokens.push(new Token(value: match[0], scopes: @scopes))
buildTabToken: (tabLength) ->
outputTokens
buildHardTabToken: (tabLength) ->
@buildTabToken(tabLength, true)
buildSoftTabToken: (tabLength) ->
@buildTabToken(tabLength, false)
buildTabToken: (tabLength, isHardTab) ->
new Token(
value: _.multiplyString(" ", tabLength)
scopes: @scopes
bufferDelta: 1
bufferDelta: if isHardTab then 1 else tabLength
isAtomic: true
isTab: true
isHardTab: isHardTab
)
isOnlyWhitespace: ->
not /\S/.test(@value)
getValueAsHtml: ({invisibles, hasLeadingWhitespace, hasTrailingWhitespace})->
html = @value
.replace(/&/g, '&amp;')
@@ -49,7 +71,7 @@ class Token
.replace(/>/g, '&gt;')
if invisibles
if @isTab and invisibles.tab
if @isHardTab and invisibles.tab
html = html.replace(/^./, "<span class='invisible'>#{invisibles.tab}</span>")
else if invisibles.space
if hasLeadingWhitespace

View File

@@ -50,27 +50,28 @@ class TokenizedBuffer
newRange.end.column = endColumn
oldRange.end.column = endColumn
@trigger("change", {oldRange, newRange})
@trigger "change", {oldRange, newRange, bufferChange: e}
getTabLength: ->
@tabLength
setTabLength: (@tabLength) ->
@screenLines = @buildScreenLinesForRows(0, @buffer.getLastRow())
@trigger "change", {oldRange: @buffer.getRange(), newRange: @buffer.getRange()}
buildScreenLinesForRows: (startRow, endRow, startingStack) ->
stack = startingStack
ruleStack = startingStack
for row in [startRow..endRow]
screenLine = @buildScreenLineForRow(row, stack)
stack = screenLine.stack
screenLine = @buildScreenLineForRow(row, ruleStack)
ruleStack = screenLine.ruleStack
screenLine
buildScreenLineForRow: (row, stack) ->
buildScreenLineForRow: (row, ruleStack) ->
line = @buffer.lineForRow(row)
{tokens, stack} = @languageMode.getLineTokens(line, stack)
tokenObjects = []
for tokenProperties in tokens
token = new Token(tokenProperties)
tokenObjects.push(token.breakOutTabCharacters(@tabLength)...)
text = _.pluck(tokenObjects, 'value').join('')
new ScreenLine(
tokens: tokenObjects
stack: stack
)
val = @languageMode.tokenizeLine(line, {ruleStack, @tabLength})
console.log val, line unless val.ruleStack
new ScreenLine(val)
lineForScreenRow: (row) ->
@screenLines[row]
@@ -79,7 +80,7 @@ class TokenizedBuffer
@screenLines[startRow..endRow]
stackForRow: (row) ->
@screenLines[row]?.stack
@screenLines[row]?.ruleStack
scopesForPosition: (position) ->
position = Point.fromObject(position)

View File

@@ -1,4 +1,4 @@
# Like sands through the hourglass, so are the days of our lives.
require 'atom'
require 'window'
window.attachRootView(window.location.params.pathToOpen)
window.attachRootView(window.location.params.pathToOpen)