aboutsummaryrefslogtreecommitdiff
path: root/atom/packages/vim-mode/lib/text-objects.coffee
blob: bf386057f583ab8832ed77db33e1daeee7ab412c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
{Range} = require 'atom'
AllWhitespace = /^\s$/
WholeWordRegex = /\S+/
{mergeRanges} = require './utils'

class TextObject
  constructor: (@editor, @state) ->

  isComplete: -> true
  isRecordable: -> false

  execute: -> @select.apply(this, arguments)

class SelectInsideWord extends TextObject
  select: ->
    for selection in @editor.getSelections()
      selection.expandOverWord()
    [true]

class SelectInsideWholeWord extends TextObject
  select: ->
    for selection in @editor.getSelections()
      range = selection.cursor.getCurrentWordBufferRange({wordRegex: WholeWordRegex})
      selection.setBufferRange(mergeRanges(selection.getBufferRange(), range))
      true

# SelectInsideQuotes and the next class defined (SelectInsideBrackets) are
# almost-but-not-quite-repeated code. They are different because of the depth
# checks in the bracket matcher.

class SelectInsideQuotes extends TextObject
  constructor: (@editor, @char, @includeQuotes) ->

  findOpeningQuote: (pos) ->
    start = pos.copy()
    pos = pos.copy()
    while pos.row >= 0
      line = @editor.lineTextForBufferRow(pos.row)
      pos.column = line.length - 1 if pos.column is -1
      while pos.column >= 0
        if line[pos.column] is @char
          if pos.column is 0 or line[pos.column - 1] isnt '\\'
            if @isStartQuote(pos)
              return pos
            else
              return @lookForwardOnLine(start)
        -- pos.column
      pos.column = -1
      -- pos.row
    @lookForwardOnLine(start)

  isStartQuote: (end) ->
    line = @editor.lineTextForBufferRow(end.row)
    numQuotes = line.substring(0, end.column + 1).replace( "'#{@char}", '').split(@char).length - 1
    numQuotes % 2

  lookForwardOnLine: (pos) ->
    line = @editor.lineTextForBufferRow(pos.row)

    index = line.substring(pos.column).indexOf(@char)
    if index >= 0
      pos.column += index
      return pos
    null

  findClosingQuote: (start) ->
    end = start.copy()
    escaping = false

    while end.row < @editor.getLineCount()
      endLine = @editor.lineTextForBufferRow(end.row)
      while end.column < endLine.length
        if endLine[end.column] is '\\'
          ++ end.column
        else if endLine[end.column] is @char
          -- start.column if @includeQuotes
          ++ end.column if @includeQuotes
          return end
        ++ end.column
      end.column = 0
      ++ end.row
    return

  select: ->
    for selection in @editor.getSelections()
      start = @findOpeningQuote(selection.cursor.getBufferPosition())
      if start?
        ++ start.column # skip the opening quote
        end = @findClosingQuote(start)
        if end?
          selection.setBufferRange(mergeRanges(selection.getBufferRange(), [start, end]))
      not selection.isEmpty()

# SelectInsideBrackets and the previous class defined (SelectInsideQuotes) are
# almost-but-not-quite-repeated code. They are different because of the depth
# checks in the bracket matcher.

class SelectInsideBrackets extends TextObject
  constructor: (@editor, @beginChar, @endChar, @includeBrackets) ->

  findOpeningBracket: (pos) ->
    pos = pos.copy()
    depth = 0
    while pos.row >= 0
      line = @editor.lineTextForBufferRow(pos.row)
      pos.column = line.length - 1 if pos.column is -1
      while pos.column >= 0
        switch line[pos.column]
          when @endChar then ++ depth
          when @beginChar
            return pos if -- depth < 0
        -- pos.column
      pos.column = -1
      -- pos.row

  findClosingBracket: (start) ->
    end = start.copy()
    depth = 0
    while end.row < @editor.getLineCount()
      endLine = @editor.lineTextForBufferRow(end.row)
      while end.column < endLine.length
        switch endLine[end.column]
          when @beginChar then ++ depth
          when @endChar
            if -- depth < 0
              -- start.column if @includeBrackets
              ++ end.column if @includeBrackets
              return end
        ++ end.column
      end.column = 0
      ++ end.row
    return

  select: ->
    for selection in @editor.getSelections()
      start = @findOpeningBracket(selection.cursor.getBufferPosition())
      if start?
        ++ start.column # skip the opening quote
        end = @findClosingBracket(start)
        if end?
          selection.setBufferRange(mergeRanges(selection.getBufferRange(), [start, end]))
      not selection.isEmpty()

class SelectAWord extends TextObject
  select: ->
    for selection in @editor.getSelections()
      selection.expandOverWord()
      loop
        endPoint = selection.getBufferRange().end
        char = @editor.getTextInRange(Range.fromPointWithDelta(endPoint, 0, 1))
        break unless AllWhitespace.test(char)
        selection.selectRight()
      true

class SelectAWholeWord extends TextObject
  select: ->
    for selection in @editor.getSelections()
      range = selection.cursor.getCurrentWordBufferRange({wordRegex: WholeWordRegex})
      selection.setBufferRange(mergeRanges(selection.getBufferRange(), range))
      loop
        endPoint = selection.getBufferRange().end
        char = @editor.getTextInRange(Range.fromPointWithDelta(endPoint, 0, 1))
        break unless AllWhitespace.test(char)
        selection.selectRight()
      true

class Paragraph extends TextObject

  select: ->
    for selection in @editor.getSelections()
      @selectParagraph(selection)

  # Return a range delimted by the start or the end of a paragraph
  paragraphDelimitedRange: (startPoint) ->
    inParagraph = @isParagraphLine(@editor.lineTextForBufferRow(startPoint.row))
    upperRow = @searchLines(startPoint.row, -1, inParagraph)
    lowerRow = @searchLines(startPoint.row, @editor.getLineCount(), inParagraph)
    new Range([upperRow + 1, 0], [lowerRow, 0])

  searchLines: (startRow, rowLimit, startedInParagraph) ->
    for currentRow in [startRow..rowLimit]
      line = @editor.lineTextForBufferRow(currentRow)
      if startedInParagraph isnt @isParagraphLine(line)
        return currentRow
    rowLimit

  isParagraphLine: (line) -> (/\S/.test(line))

class SelectInsideParagraph extends Paragraph
  selectParagraph: (selection) ->
    oldRange = selection.getBufferRange()
    startPoint = selection.cursor.getBufferPosition()
    newRange = @paragraphDelimitedRange(startPoint)
    selection.setBufferRange(mergeRanges(oldRange, newRange))
    true

class SelectAParagraph extends Paragraph
  selectParagraph: (selection) ->
    oldRange = selection.getBufferRange()
    startPoint = selection.cursor.getBufferPosition()
    newRange = @paragraphDelimitedRange(startPoint)
    nextRange = @paragraphDelimitedRange(newRange.end)
    selection.setBufferRange(mergeRanges(oldRange, [newRange.start, nextRange.end]))
    true

module.exports = {TextObject, SelectInsideWord, SelectInsideWholeWord, SelectInsideQuotes,
  SelectInsideBrackets, SelectAWord, SelectAWholeWord, SelectInsideParagraph, SelectAParagraph}