mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-20 15:37:46 +03:00
Merge branch 'master' into config
This commit is contained in:
commit
bbd2e384c5
@ -57,10 +57,7 @@ class OnigScannerUserData : public CefBase {
|
||||
bool useCachedResult = false;
|
||||
OnigResult *result = NULL;
|
||||
|
||||
// In Oniguruma, \G is based on the start position of the match, so the result
|
||||
// changes based on the start position. So it can't be cached.
|
||||
BOOL containsBackslashG = [regExp.expression rangeOfString:@"\\G"].location != NSNotFound;
|
||||
if (useCachedResults && index <= maxCachedIndex && ! containsBackslashG) {
|
||||
if (useCachedResults && index <= maxCachedIndex) {
|
||||
result = cachedResults[index];
|
||||
useCachedResult = (result == NULL || [result locationAt:0] >= startLocation);
|
||||
}
|
||||
@ -158,4 +155,4 @@ bool OnigScanner::Execute(const CefString& name,
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace v8_extensions
|
||||
} // namespace v8_extensions
|
||||
|
@ -1728,14 +1728,14 @@ describe "Editor", ->
|
||||
describe "when there is no wrapping", ->
|
||||
it "highlights the line where the initial cursor position is", ->
|
||||
expect(editor.getCursorBufferPosition().row).toBe 0
|
||||
expect(editor.find('.line-number.cursor-line').length).toBe 1
|
||||
expect(editor.find('.line-number.cursor-line').text()).toBe "1"
|
||||
expect(editor.find('.line-number.cursor-line.cursor-line-no-selection').length).toBe 1
|
||||
expect(editor.find('.line-number.cursor-line.cursor-line-no-selection').text()).toBe "1"
|
||||
|
||||
it "updates the highlighted line when the cursor position changes", ->
|
||||
editor.setCursorBufferPosition([1,0])
|
||||
expect(editor.getCursorBufferPosition().row).toBe 1
|
||||
expect(editor.find('.line-number.cursor-line').length).toBe 1
|
||||
expect(editor.find('.line-number.cursor-line').text()).toBe "2"
|
||||
expect(editor.find('.line-number.cursor-line.cursor-line-no-selection').length).toBe 1
|
||||
expect(editor.find('.line-number.cursor-line.cursor-line-no-selection').text()).toBe "2"
|
||||
|
||||
describe "when there is wrapping", ->
|
||||
beforeEach ->
|
||||
@ -1745,23 +1745,28 @@ describe "Editor", ->
|
||||
|
||||
it "highlights the line where the initial cursor position is", ->
|
||||
expect(editor.getCursorBufferPosition().row).toBe 0
|
||||
expect(editor.find('.line-number.cursor-line').length).toBe 1
|
||||
expect(editor.find('.line-number.cursor-line').text()).toBe "1"
|
||||
expect(editor.find('.line-number.cursor-line.cursor-line-no-selection').length).toBe 1
|
||||
expect(editor.find('.line-number.cursor-line.cursor-line-no-selection').text()).toBe "1"
|
||||
|
||||
it "updates the highlighted line when the cursor position changes", ->
|
||||
editor.setCursorBufferPosition([1,0])
|
||||
expect(editor.getCursorBufferPosition().row).toBe 1
|
||||
expect(editor.find('.line-number.cursor-line').length).toBe 1
|
||||
expect(editor.find('.line-number.cursor-line').text()).toBe "2"
|
||||
expect(editor.find('.line-number.cursor-line.cursor-line-no-selection').length).toBe 1
|
||||
expect(editor.find('.line-number.cursor-line.cursor-line-no-selection').text()).toBe "2"
|
||||
|
||||
describe "when the selection spans multiple lines", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom(30)
|
||||
|
||||
it "doesn't highlight the background or the gutter", ->
|
||||
it "highlights the foreground of the gutter", ->
|
||||
editor.getSelection().setBufferRange(new Range([0,0],[2,0]))
|
||||
expect(editor.getSelection().isSingleScreenLine()).toBe false
|
||||
expect(editor.find('.line-number.cursor-line').length).toBe 0
|
||||
expect(editor.find('.line-number.cursor-line').length).toBe 3
|
||||
|
||||
it "doesn't highlight the background of the gutter", ->
|
||||
editor.getSelection().setBufferRange(new Range([0,0],[2,0]))
|
||||
expect(editor.getSelection().isSingleScreenLine()).toBe false
|
||||
expect(editor.find('.line-number.cursor-line.cursor-line-no-selection').length).toBe 0
|
||||
|
||||
it "when a newline is deleted with backspace, the line number of the new cursor position is highlighted", ->
|
||||
editor.setCursorScreenPosition([1,0])
|
||||
@ -1985,3 +1990,15 @@ describe "Editor", ->
|
||||
|
||||
runs ->
|
||||
expect(editor.getText()).toBe(originalPathText)
|
||||
|
||||
describe "when clicking a gutter line", ->
|
||||
it "moves the cursor to the start of the selected line", ->
|
||||
rootView.attachToDom()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [0,0]
|
||||
editor.gutter.find(".line-number:eq(1)").trigger 'click'
|
||||
expect(editor.getCursorScreenPosition()).toEqual [1,0]
|
||||
|
||||
it "selects to the start of the selected line when shift is pressed", ->
|
||||
expect(editor.getSelection().getScreenRange()).toEqual [0,0], [0,0]
|
||||
editor.gutter.find(".line-number:eq(1)").trigger 'click', {shiftKey: true}
|
||||
expect(editor.getSelection().getScreenRange()).toEqual [0,0], [1,0]
|
||||
|
@ -14,7 +14,7 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
fullyTokenize = (tokenizedBuffer) ->
|
||||
advanceClock() while tokenizedBuffer.firstInvalidRow()?
|
||||
changeHandler.reset()
|
||||
changeHandler?.reset()
|
||||
|
||||
describe "when the buffer contains soft-tabs", ->
|
||||
beforeEach ->
|
||||
@ -326,3 +326,60 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{tabAsSpaces} buy()#{tabAsSpaces}while supply > demand"
|
||||
|
||||
describe "when a Git commit message file is tokenized", ->
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('COMMIT_EDITMSG', autoIndent: false)
|
||||
buffer = editSession.buffer
|
||||
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
|
||||
editSession.setVisible(true)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
afterEach ->
|
||||
editSession.destroy()
|
||||
|
||||
it "correctly parses a long line", ->
|
||||
longLine = tokenizedBuffer.lineForScreenRow(0)
|
||||
expect(longLine.text).toBe "longggggggggggggggggggggggggggggggggggggggggggggggg"
|
||||
{ tokens } = longLine
|
||||
|
||||
expect(tokens[0].value).toBe "longggggggggggggggggggggggggggggggggggggggggggggggg"
|
||||
expect(tokens[0].scopes).toEqual ["text.git-commit", "meta.scope.message.git-commit", "invalid.deprecated.line-too-long.git-commit"]
|
||||
|
||||
it "correctly parses the number sign of the first comment line", ->
|
||||
commentLine = tokenizedBuffer.lineForScreenRow(1)
|
||||
expect(commentLine.text).toBe "# Please enter the commit message for your changes. Lines starting"
|
||||
{ tokens } = commentLine
|
||||
|
||||
expect(tokens[0].value).toBe "#"
|
||||
expect(tokens[0].scopes).toEqual ["text.git-commit", "meta.scope.metadata.git-commit", "comment.line.number-sign.git-commit", "punctuation.definition.comment.git-commit"]
|
||||
|
||||
describe "when a C++ source file is tokenized", ->
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('includes.cc', autoIndent: false)
|
||||
buffer = editSession.buffer
|
||||
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
|
||||
editSession.setVisible(true)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
afterEach ->
|
||||
editSession.destroy()
|
||||
|
||||
it "correctly parses the first include line", ->
|
||||
longLine = tokenizedBuffer.lineForScreenRow(0)
|
||||
expect(longLine.text).toBe '#include "a.h"'
|
||||
{ tokens } = longLine
|
||||
|
||||
expect(tokens[0].value).toBe "#"
|
||||
expect(tokens[0].scopes).toEqual ["source.c++", "meta.preprocessor.c.include"]
|
||||
expect(tokens[1].value).toBe 'include'
|
||||
expect(tokens[1].scopes).toEqual ["source.c++", "meta.preprocessor.c.include", "keyword.control.import.include.c"]
|
||||
|
||||
it "correctly parses the second include line", ->
|
||||
commentLine = tokenizedBuffer.lineForScreenRow(1)
|
||||
expect(commentLine.text).toBe '#include "b.h"'
|
||||
{ tokens } = commentLine
|
||||
|
||||
expect(tokens[0].value).toBe "#"
|
||||
expect(tokens[0].scopes).toEqual ["source.c++", "meta.preprocessor.c.include"]
|
||||
expect(tokens[1].value).toBe 'include'
|
||||
expect(tokens[1].scopes).toEqual ["source.c++", "meta.preprocessor.c.include", "keyword.control.import.include.c"]
|
||||
|
2
spec/fixtures/COMMIT_EDITMSG
vendored
Normal file
2
spec/fixtures/COMMIT_EDITMSG
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
longggggggggggggggggggggggggggggggggggggggggggggggg
|
||||
# Please enter the commit message for your changes. Lines starting
|
2
spec/fixtures/includes.cc
vendored
Normal file
2
spec/fixtures/includes.cc
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
#include "a.h"
|
||||
#include "b.h"
|
@ -126,4 +126,4 @@ describe 'Child Processes', ->
|
||||
ChildProcess.exec(cmd, options)
|
||||
|
||||
runs ->
|
||||
expect(output.length).toBeGreaterThan 1
|
||||
expect(output.length).toBeGreaterThan 1
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
Range = require 'range'
|
||||
Point = require 'point'
|
||||
|
||||
module.exports =
|
||||
class Gutter extends View
|
||||
@ -18,9 +20,17 @@ class Gutter extends View
|
||||
@attached = true
|
||||
|
||||
editor = @editor()
|
||||
highlightCursorLine = => @highlightCursorLine()
|
||||
editor.on 'cursor-move', highlightCursorLine
|
||||
editor.on 'selection-change', highlightCursorLine
|
||||
highlightLines = => @highlightLines()
|
||||
editor.on 'cursor-move', highlightLines
|
||||
editor.on 'selection-change', highlightLines
|
||||
@on 'click', '.line-number', (e) =>
|
||||
row = parseInt($(e.target).text()) - 1
|
||||
position = new Point(row, 0)
|
||||
if e.shiftKey
|
||||
@editor().selectToScreenPosition(position)
|
||||
else
|
||||
@editor().setCursorScreenPosition(position)
|
||||
|
||||
@calculateWidth()
|
||||
|
||||
editor: ->
|
||||
@ -63,8 +73,8 @@ class Gutter extends View
|
||||
@calculateWidth()
|
||||
@firstScreenRow = startScreenRow
|
||||
@lastScreenRow = endScreenRow
|
||||
@highlightedRow = null
|
||||
@highlightCursorLine()
|
||||
@highlightedRows = null
|
||||
@highlightLines()
|
||||
|
||||
calculateWidth: ->
|
||||
highestNumberWidth = @editor().getLineCount().toString().length * @editor().charWidth
|
||||
@ -73,17 +83,38 @@ class Gutter extends View
|
||||
@lineNumbers.width(highestNumberWidth + @calculateLineNumberPadding())
|
||||
@widthChanged?(@outerWidth())
|
||||
|
||||
highlightCursorLine: ->
|
||||
if @editor().getSelection().isEmpty()
|
||||
rowToHighlight = @editor().getCursorScreenPosition().row
|
||||
return if rowToHighlight == @highlightedRow
|
||||
return if rowToHighlight < @firstScreenRow or rowToHighlight > @lastScreenRow
|
||||
removeLineHighlights: ->
|
||||
return unless @highlightedLineNumbers
|
||||
for line in @highlightedLineNumbers
|
||||
line.classList.remove('cursor-line')
|
||||
line.classList.remove('cursor-line-no-selection')
|
||||
@highlightedLineNumbers = null
|
||||
|
||||
@highlightedLineNumber?.classList.remove('cursor-line')
|
||||
if @highlightedLineNumber = @lineNumbers[0].children[rowToHighlight - @firstScreenRow]
|
||||
@highlightedLineNumber.classList.add('cursor-line')
|
||||
@highlightedRow = rowToHighlight
|
||||
addLineHighlight: (row, emptySelection) ->
|
||||
return if row < @firstScreenRow or row > @lastScreenRow
|
||||
@highlightedLineNumbers ?= []
|
||||
if highlightedLineNumber = @lineNumbers[0].children[row - @firstScreenRow]
|
||||
highlightedLineNumber.classList.add('cursor-line')
|
||||
highlightedLineNumber.classList.add('cursor-line-no-selection') if emptySelection
|
||||
@highlightedLineNumbers.push(highlightedLineNumber)
|
||||
|
||||
highlightLines: ->
|
||||
if @editor().getSelection().isEmpty()
|
||||
row = @editor().getCursorScreenPosition().row
|
||||
rowRange = new Range([row, 0], [row, 0])
|
||||
return if @selectionEmpty and @highlightedRows?.isEqual(rowRange)
|
||||
|
||||
@removeLineHighlights()
|
||||
@addLineHighlight(row, true)
|
||||
@highlightedRows = rowRange
|
||||
@selectionEmpty = true
|
||||
else
|
||||
@highlightedLineNumber?.classList.remove('cursor-line')
|
||||
@highlightedLineNumber = null
|
||||
@highlightedRow = null
|
||||
selectedRows = @editor().getSelection().getScreenRange()
|
||||
selectedRows = new Range([selectedRows.start.row, 0], [selectedRows.end.row, 0])
|
||||
return if not @selectionEmpty and @highlightedRows?.isEqual(selectedRows)
|
||||
|
||||
@removeLineHighlights()
|
||||
for row in [selectedRows.start.row..selectedRows.end.row]
|
||||
@addLineHighlight(row, false)
|
||||
@highlightedRows = selectedRows
|
||||
@selectionEmpty = false
|
||||
|
@ -186,5 +186,5 @@ class LanguageMode
|
||||
if desiredIndentLevel < currentIndentLevel
|
||||
@editSession.setIndentationForBufferRow(bufferRow, desiredIndentLevel)
|
||||
|
||||
tokenizeLine: (line, stack) ->
|
||||
{tokens, stack} = @grammar.tokenizeLine(line, stack)
|
||||
tokenizeLine: (line, stack, firstLine) ->
|
||||
{tokens, stack} = @grammar.tokenizeLine(line, stack, firstLine)
|
||||
|
@ -29,8 +29,7 @@ class TextMateGrammar
|
||||
data = {patterns: [data], tempName: name} if data.begin? or data.match?
|
||||
@repository[name] = new Rule(this, data)
|
||||
|
||||
tokenizeLine: (line, ruleStack=[@initialRule]) ->
|
||||
ruleStack ?= [@initialRule]
|
||||
tokenizeLine: (line, ruleStack=[@initialRule], firstLine=false) ->
|
||||
ruleStack = new Array(ruleStack...) # clone ruleStack
|
||||
tokens = []
|
||||
position = 0
|
||||
@ -42,9 +41,9 @@ class TextMateGrammar
|
||||
tokens = [new Token(value: "", scopes: scopes)]
|
||||
return { tokens, ruleStack }
|
||||
|
||||
break if position == line.length
|
||||
break if position == line.length + 1 # include trailing newline position
|
||||
|
||||
if match = _.last(ruleStack).getNextTokens(ruleStack, line, position)
|
||||
if match = _.last(ruleStack).getNextTokens(ruleStack, line, position, firstLine)
|
||||
{ nextTokens, tokensStartPosition, tokensEndPosition } = match
|
||||
if position < tokensStartPosition # unmatched text before next tokens
|
||||
tokens.push(new Token(
|
||||
@ -56,12 +55,14 @@ class TextMateGrammar
|
||||
position = tokensEndPosition
|
||||
|
||||
else # push filler token for unmatched text at end of line
|
||||
tokens.push(new Token(
|
||||
value: line[position...line.length]
|
||||
scopes: scopes
|
||||
))
|
||||
if position < line.length
|
||||
tokens.push(new Token(
|
||||
value: line[position...line.length]
|
||||
scopes: scopes
|
||||
))
|
||||
break
|
||||
|
||||
ruleStack.forEach (rule) -> rule.clearAnchorPosition()
|
||||
{ tokens, ruleStack }
|
||||
|
||||
ruleForInclude: (name) ->
|
||||
@ -79,6 +80,7 @@ class Rule
|
||||
patterns: null
|
||||
allPatterns: null
|
||||
createEndPattern: null
|
||||
anchorPosition: -1
|
||||
|
||||
constructor: (@grammar, {@scopeName, patterns, @endPattern}) ->
|
||||
patterns ?= []
|
||||
@ -95,14 +97,30 @@ class Rule
|
||||
@allPatterns.push(pattern.getIncludedPatterns(included)...)
|
||||
@allPatterns
|
||||
|
||||
getScanner: ->
|
||||
@scanner ?= new OnigScanner(_.pluck(@getIncludedPatterns(), 'regexSource'))
|
||||
clearAnchorPosition: -> @anchorPosition = -1
|
||||
|
||||
getNextTokens: (stack, line, position) ->
|
||||
getScanner: (position, firstLine) ->
|
||||
return @scanner if @scanner
|
||||
|
||||
anchored = false
|
||||
regexes = []
|
||||
@getIncludedPatterns().forEach (pattern) =>
|
||||
if pattern.anchored
|
||||
anchored = true
|
||||
regex = pattern.replaceAnchor(firstLine, position, @anchorPosition)
|
||||
else
|
||||
regex = pattern.regexSource
|
||||
regexes.push regex if regex
|
||||
|
||||
regexScanner = new OnigScanner(regexes)
|
||||
@scanner = regexScanner unless anchored
|
||||
regexScanner
|
||||
|
||||
getNextTokens: (stack, line, position, firstLine) ->
|
||||
patterns = @getIncludedPatterns()
|
||||
|
||||
# Add a `\n` to appease patterns that contain '\n' explicitly
|
||||
return null unless result = @getScanner().findNextMatch(line + "\n", position)
|
||||
return null unless result = @getScanner(position, firstLine).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
|
||||
@ -130,6 +148,7 @@ class Pattern
|
||||
scopeName: null
|
||||
captures: null
|
||||
backReferences: null
|
||||
anchored: false
|
||||
|
||||
constructor: (@grammar, { name, contentName, @include, match, begin, end, captures, beginCaptures, endCaptures, patterns, @popRule, hasBackReferences}) ->
|
||||
@scopeName = name ? contentName # TODO: We need special treatment of contentName
|
||||
@ -144,6 +163,42 @@ class Pattern
|
||||
@captures = beginCaptures ? captures
|
||||
endPattern = new Pattern(@grammar, { match: end, captures: endCaptures ? captures, popRule: true})
|
||||
@pushRule = new Rule(@grammar, { @scopeName, patterns, endPattern })
|
||||
@anchored = @hasAnchor()
|
||||
|
||||
hasAnchor: ->
|
||||
return false unless @regexSource
|
||||
escape = false
|
||||
for character in @regexSource.split('')
|
||||
return true if escape and 'AGz'.indexOf(character) isnt -1
|
||||
escape = not escape and character is '\\'
|
||||
false
|
||||
|
||||
replaceAnchor: (firstLine, offset, anchor) ->
|
||||
escaped = []
|
||||
placeholder = '\uFFFF'
|
||||
escape = false
|
||||
for character in @regexSource.split('')
|
||||
if escape
|
||||
switch character
|
||||
when 'A'
|
||||
if firstLine
|
||||
escaped.push("\\#{character}")
|
||||
else
|
||||
escaped.push(placeholder)
|
||||
when 'G'
|
||||
if offset is anchor
|
||||
escaped.push("\\#{character}")
|
||||
else
|
||||
escaped.push(placeholder)
|
||||
when 'z' then escaped.push('$(?!\n)(?<!\n)')
|
||||
else escaped.push("\\#{character}")
|
||||
escape = false
|
||||
else if character is '\\'
|
||||
escape = true
|
||||
else
|
||||
escaped.push(character)
|
||||
|
||||
escaped.join('')
|
||||
|
||||
resolveBackReferences: (line, beginCaptureIndices) ->
|
||||
beginCaptures = []
|
||||
@ -180,7 +235,9 @@ class Pattern
|
||||
else
|
||||
tokens = [new Token(value: line[start...end], scopes: scopes)]
|
||||
if @pushRule
|
||||
stack.push(@pushRule.getRuleToPush(line, captureIndices))
|
||||
ruleToPush = @pushRule.getRuleToPush(line, captureIndices)
|
||||
ruleToPush.anchorPosition = captureIndices[2]
|
||||
stack.push(ruleToPush)
|
||||
else if @popRule
|
||||
stack.pop()
|
||||
|
||||
@ -226,4 +283,3 @@ shiftCapture = (captureIndices) ->
|
||||
|
||||
scopesFromStack = (stack) ->
|
||||
_.compact(_.pluck(stack, "scopeName"))
|
||||
|
||||
|
@ -134,7 +134,7 @@ class TokenizedBuffer
|
||||
|
||||
buildTokenizedScreenLineForRow: (row, ruleStack) ->
|
||||
line = @buffer.lineForRow(row)
|
||||
{ tokens, ruleStack } = @languageMode.tokenizeLine(line, ruleStack)
|
||||
{ tokens, ruleStack } = @languageMode.tokenizeLine(line, ruleStack, row is 0)
|
||||
new ScreenLine({tokens, ruleStack, @tabLength})
|
||||
|
||||
lineForScreenRow: (row) ->
|
||||
|
@ -33,7 +33,8 @@
|
||||
color: rgba(255, 255, 255, .6);
|
||||
}
|
||||
|
||||
.editor.focused .cursor-line {
|
||||
.editor.focused .line-number.cursor-line-no-selection,
|
||||
.editor.focused .line.cursor-line {
|
||||
background-color: rgba(255, 255, 255, .12);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user