def getBoundTextRangeSL(leftBoundary, rightBoundary, pos, doc): ''' Get the range between any symbol specified in leftBoundary set and rightBoundary Search starts from given cursor position... NOTE `SL' suffix means Single Line -- i.e. when search, do not cross one line boundaries! ''' if not doc.lineLength(pos.line()): return KTextEditor.Range(pos, pos) lineStr = doc.line(pos.line()) # Get the current line as string to analyse found = False # NOTE If cursor positioned at the end of a line, column() # will be equal to the line length and lineStr[cc] will # fail... So it must be handled before the `for' loop... initialPos = min(len(lineStr) - 1, pos.column() - 1) # Let initial index be less than line length for cc in range(initialPos, -1, -1): # Moving towards the line start found = lineStr[ cc] in leftBoundary # Check the current char for left boundary terminators if found: break # Break the loop if found smth startPos = KTextEditor.Cursor(pos.line(), cc + int(found)) cc = pos.column() for cc in range(pos.column(), len(lineStr)): # Moving towards the line end if lineStr[ cc] in rightBoundary: # Check the current char for right boundary terminators break # Break the loop if found smth endPos = KTextEditor.Cursor(pos.line(), cc) return KTextEditor.Range(startPos, endPos)
def insertColor(): ''' Insert/edit #color using color chooser dialog If cursor positioned in a #color 'word', this action will edit it, otherwise a new #color will be inserted into a document. ''' document = kate.activeDocument() view = kate.activeView() cursor = view.cursorPosition() if not view.selection(): # If no selection, try to get a #color under cursor colorRange = common.getBoundTextRangeSL(_LEFT_COLOR_BOUNDARY, _RIGHT_COLOR_BOUNDARY, cursor, document) else: # Some text selected, just use it as input... colorRange = view.selectionRange() if colorRange.isValid(): currentColor = document.text(colorRange) else: currentColor = kate.configuration[_INSERT_COLOR_LCC] color = QtGui.QColor(currentColor) # If no text selected (i.e. user don't want to override selection) # and (guessed) #color string is not valid, entered color will # be inserted at the current cursor position. if not color.isValid(): color = QtGui.QColor(kate.configuration[_INSERT_COLOR_LCC]) if not view.selection(): colorRange = KTextEditor.Range( cursor, cursor) # Will not override the text under cursor... # Choose a #color via dialog result = kdeui.KColorDialog.getColor(color) if result == kdeui.KColorDialog.Accepted: # Is user pressed Ok? colorStr = color.name() # Get it as #color text # Remember for future use kate.configuration[_INSERT_COLOR_LCC] = colorStr document.startEditing() document.replaceText( colorRange, colorStr) # Replace selected/found range w/ a new text document.endEditing() # Select just entered #color, if smth was selected before if view.selection(): startPos = colorRange.start() endPos = KTextEditor.Cursor(startPos.line(), startPos.column() + len(colorStr)) view.setSelection(KTextEditor.Range(startPos, endPos))
def removeBlock(): ''' Remove a block of code commented with #if0 or #if1-#else''' document = kate.activeDocument() view = kate.activeView() # Make list of ranges of #if*/#endif blocks blocksList = buildIfEndifMap(document) # Locate a block where cursor currently positioned idx = locateBlock(view.cursorPosition().line(), blocksList, False) if idx != -1: # Get current value v = BLOCK_START_SEARCH_RE.search(str(document.line( blocksList[idx][0]))).group(1) # Do nothing if it's not a #if0/#if1 if v not in ('0', 'false', '1', 'true'): return document.startEditing() # Start edit transaction # What to remove? if v in ('0', 'false'): # Remove `then` part if blocksList[idx][2] != -1: # Is there `#else` part? # Yeah! Remove `#endif` line and then from `#if` to `#else` (including) document.removeLine(blocksList[idx][1]) r = KTextEditor.Range(blocksList[idx][0], 0, blocksList[idx][2] + 1, 0) else: # No! So just remove whole block r = KTextEditor.Range(blocksList[idx][0], 0, blocksList[idx][1] + 1, 0) document.removeText(r) else: if blocksList[idx][2] != -1: # Is there `#else` part? # Yeah! Remove from `#else` to `#endif` block and then `#if` line r = KTextEditor.Range(blocksList[idx][2], 0, blocksList[idx][1] + 1, 0) document.removeText(r) document.removeLine(blocksList[idx][0]) else: # No! Ok just remove `#endif` line and then `#if` document.removeLine(blocksList[idx][1]) document.removeLine(blocksList[idx][0]) document.endEditing() # End transaction else: ui.popup("Oops", "It seems cursor positioned out of any #if0/#if1 block", "face-sad")
def updateColors(self, view=None): """Scan a document for #colors""" self.colors = list() # Clear previous colors if view: document = view.document() else: try: document = kate.activeDocument() except kate.NoActiveView: return # Do nothing if we can't get a current document # Iterate over document's lines trying to find #colors for l in range(0, document.lines()): line = document.line(l) # Get the current line start = 0 # Set initial position to 0 (line start) while start < len(line): # Repeat 'till the line end start = line.find( '#', start) # Try to find a '#' character (start of #color) if start == -1: # Did we found smth? break # No! Nothing to do... # Try to get a word right after the '#' char end = start + 1 for c in line[end:]: if not (c in string.hexdigits or c in string.ascii_letters): break end += 1 color_range = KTextEditor.Range(l, start, l, end) color_str = document.text(color_range) color = QColor(color_str) if color.isValid(): self.colors.append(ColorRangePair(color, color_range)) print('PALETTE VIEW: Found %s' % color_str) start = end
def boostFormatText(textRange, indent, breakPositions): document = kate.activeDocument() originalText = document.text(textRange) print("Original text:\n'" + originalText + "'") # Slice text whithin a given range into pieces to be realigned ranges = list() prevPos = textRange.start() breakCh = None indentStr = ' ' * (indent + 2) breakPositions.append(textRange.end()) for b in breakPositions: print("* prev pos: " + str(prevPos.line()) + ", " + str(prevPos.column())) print("* current pos: " + str(b.line()) + ", " + str(b.column())) chunk = (document.text(KTextEditor.Range(prevPos, b))).strip() print("* current chunk:\n'" + chunk + "'") t = ('\n ').join(chunk.splitlines()) print("* current line:\n'" + t + "'") if breakCh: outText += indentStr + breakCh + ' ' + t + '\n' else: outText = '\n' + indentStr + ' ' + t + '\n' breakCh = document.character(b) prevPos = KTextEditor.Cursor(b.line(), b.column() + 1) outText += indentStr print("Out text:\n'" + outText + "'") if outText != originalText: document.startEditing() document.replaceText(textRange, outText) document.endEditing()
def insertColor(): """Insert/edit color string using color chooser dialog If cursor positioned in a color string, this action will edit it, otherwise a new color string will be inserted into a document. """ document = kate.activeDocument() view = kate.activeView() cursor = view.cursorPosition() if view.selection(): # Some text selected, just use it as input... color_range = view.selectionRange() else: # If no selection, try to get a #color under cursor color_range = common.getBoundTextRangeSL( common.IDENTIFIER_BOUNDARIES - {'#'}, common.IDENTIFIER_BOUNDARIES, cursor, document) if color_range.isValid(): current_color = document.text(color_range) else: current_color = kate.configuration[_INSERT_COLOR_LCC] color = QColor(current_color) # If no text selected (i.e. user doesn’t want to override selection) # and (guessed) color string is not valid, entered color will # be inserted at the current cursor position. if not color.isValid(): color = QColor(kate.configuration[_INSERT_COLOR_LCC]) if not view.selection(): color_range = KTextEditor.Range( cursor, 0) # Will not override the text under cursor… # Choose a color via dialog result = KColorDialog.getColor(color) if result == KColorDialog.Accepted: # Did user press OK? color_str = color.name() # Get it as color string # Remember for future use kate.configuration[_INSERT_COLOR_LCC] = color_str document.startEditing() document.replaceText( color_range, color_str) # Replace selected/found range w/ a new text document.endEditing() # Select just entered #color, if something was selected before if view.selection(): start_pos = color_range.start() view.setSelection(KTextEditor.Range(start_pos, len(color_str)))
def setSelectionFromCurrentPosition(start, end, pos=None): view = kate.activeView() pos = pos or view.cursorPosition() cursor1 = KTextEditor.Cursor(pos.line() + start[0], pos.column() + start[1]) cursor2 = KTextEditor.Cursor(pos.line() + end[0], pos.column() + end[1]) view.setSelection(KTextEditor.Range(cursor1, cursor2)) view.setCursorPosition(cursor1)
def getTextBlockAroundCursor(doc, pos, upPred, downPred): start = _getTextBlockAroundCursor(doc, pos.line(), doc.lines(), TOWARDS_START, upPred) end = _getTextBlockAroundCursor(doc, pos.line(), doc.lines(), TOWARDS_END, downPred) if start != end: start += 1 print('** getTextBlockAroundCursor[pos=%d,%d]: (%d,%d), (%d,%d)' % (pos.line(), pos.column(), start, 0, end, 0)) return KTextEditor.Range(start, 0, end, 0)
def killRestOfLine(): '''Remove text from cursor position to the end of the current line''' doc = kate.activeDocument() view = kate.activeView() pos = view.cursorPosition() endPosition = KTextEditor.Cursor(pos.line(), doc.lineLength(pos.line())) killRange = KTextEditor.Range(pos, endPosition) doc.startEditing() doc.removeText(killRange) doc.endEditing()
def turnFromBlockComment(): document = kate.activeDocument() view = kate.activeView() pos = view.cursorPosition() if view.selection(): sr = view.selectionRange() start = sr.start().line() end = sr.end().line() else: # Try to detect block comment (/* ... */) r = common.getTextBlockAroundCursor( document, pos, [pred.blockCommentStart, neg(pred.startsWith('*'))], [pred.blockCommentEnd, neg(pred.startsWith('*'))]) start = r.start().line() - 1 end = r.end().line() + 1 # Replace comments insertionText = list() align = None for i in range(start, end): line = str(document.line(i)) sline = line.lstrip() if align == None: align = ' ' * (len(line) - len(sline)) if sline.startswith('/**') or sline.startswith('*/'): continue if sline.startswith('*'): insertionText.append(align + sline.replace('*', '///', 1)) originRange = KTextEditor.Range(start, 0, end, 0) pos.setPosition(start, len(align) + 3) insertPos = KTextEditor.Cursor(start, 0) # Update the document if bool(insertionText): document.startEditing() # Start edit transaction: document.removeText(originRange) # Remove current line # insert resulting text line by line... document.insertText(insertPos, '\n'.join(insertionText) + '\n') # Move cursor to desired position view.setCursorPosition(pos) document.endEditing() # End transaction
def _wrapBlockWithChar(openCh, closeCh, indentMultiline=True): '''Wrap a current word or selection (if any) into given open and close chars If current selection is multiline, add one indentation level and put open/close chars on separate lines ''' doc = kate.activeDocument() view = kate.activeView() pos = view.cursorPosition() selectedRange = view.selectionRange() if selectedRange.isEmpty(): # No text selected. Ok, lets wrap a word where cursor positioned wordRange = common.getBoundTextRangeSL( common.CXX_IDENTIFIER_BOUNDARIES, common.CXX_IDENTIFIER_BOUNDARIES, pos, doc) _wrapRange(wordRange, openCh, closeCh, doc) else: if selectedRange.start().line() == selectedRange.end().line( ) or indentMultiline == False: # single line selection (or no special indentation required) _wrapRange(selectedRange, openCh, closeCh, doc) # extend current selection selectedRange.end().setColumn(selectedRange.end().column() + len(openCh) + len(closeCh)) view.setSelection(selectedRange) else: # Try to extend selection to be started from 0 columns at both ends common.extendSelectionToWholeLine(view) selectedRange = view.selectionRange() # multiline selection # 0) extend selection to capture whole lines gap = ' ' * common.getLineIndentation(selectedRange.start().line(), doc) # TODO Get indent width (and char) from config (get rid of hardcocded '4') text = gap + openCh + '\n' \ + '\n'.join([' ' * 4 + line for line in doc.text(selectedRange).split('\n')[:-1]]) \ + '\n' + gap + closeCh + '\n' doc.startEditing() doc.replaceText(selectedRange, text) doc.endEditing() # extend current selection r = KTextEditor.Range(selectedRange.start().line(), 0, selectedRange.end().line() + 2, 0) view.setSelection(r)
def turnToBlockComment(): document = kate.activeDocument() view = kate.activeView() pos = view.cursorPosition() if view.selection(): sr = view.selectionRange() start = sr.start().line() end = sr.end().line() else: r = common.getTextBlockAroundCursor( document, pos, [neg(any_of(pred.startsWith('///'), pred.startsWith('//!')))], [neg(any_of(pred.startsWith('///'), pred.startsWith('//!')))]) start = r.start().line() end = r.end().line() # Replace comments in every line insertionText = list() align = None for i in range(start, end): line = str(document.line(i)) sline = line.lstrip() if align == None: align = ' ' * (len(line) - len(sline)) insertionText.append( align + sline.replace('///', ' *', 1).replace('//!', ' *', 1)) originRange = KTextEditor.Range(start, 0, end, 0) pos.setPosition(start + 1, len(align) + 3) insertPos = KTextEditor.Cursor(start, 0) # Update the document if bool(insertionText): document.startEditing() # Start edit transaction: document.removeText(originRange) # Remove current line # insert resulting text ... document.insertText( insertPos, align + '/**\n' + '\n'.join(insertionText) + '\n' + align + ' */\n') # Move cursor to desired position view.setCursorPosition(pos) document.endEditing() # End transaction
def updateColorCells(self): """Calculate rows*columns and fill the cells w/ #colors""" if len(self.colors): # Recalculate rows/columns columns = int(math.sqrt(len(self.colors))) rows = int(len(self.colors) / columns) + int( bool(len(self.colors) % columns)) else: columns = 1 rows = 1 self.colors.append(ColorRangePair(QColor(), KTextEditor.Range())) self.colorCellsWidget.setColumnCount(columns) self.colorCellsWidget.setRowCount(rows) self.colorCellsWidget.resizeColumnsToContents() self.colorCellsWidget.resizeRowsToContents() # Fill color cells for i, crp in enumerate(self.colors): self.colorCellsWidget.setColor(i, crp.color)
def selectBlock(): '''Set selection of a current (where cursor positioned) #if0/#endif block''' document = kate.activeDocument() view = kate.activeView() # Make list of ranges of #if*/#endif blocks blocksList = buildIfEndifMap(document) # Locate a block where cursor currently positioned idx = locateBlock(view.cursorPosition().line(), blocksList) if idx != -1: r = KTextEditor.Range(blocksList[idx][0], 0, blocksList[idx][1] + 1, 0) view.setSelection(r) else: ui.popup("Oops", "It seems cursor positioned out of any #if0/#if1 block", "face-sad")
def killLeadOfLine(): ''' Remove text from a start of a line to the current cursor position but keep leading spaces (to avoid breaking indentation) NOTE This function suppose spaces as indentation character! TODO Get indent character from config ''' doc = kate.activeDocument() view = kate.activeView() pos = view.cursorPosition() indent = common.getCurrentLineIndentation(view) startPosition = KTextEditor.Cursor(pos.line(), 0) killRange = KTextEditor.Range(startPosition, pos) doc.startEditing() doc.removeText(killRange) doc.insertText(startPosition, ' ' * indent) doc.endEditing()
def getRangeTopology(breakChars): '''Get range opened w/ `openCh' and closed w/ `closeCh' @return tuple w/ current range, list of nested ranges and list of positions of break characters @note Assume cursor positioned whithin that range already. ''' document = kate.activeDocument() view = kate.activeView() pos = view.cursorPosition() stack = list() nestedRanges = list() breakPositions = list() firstIteration = True found = False # Iterate from the current line towards a document start for cl in range(pos.line(), -1, -1): lineStr = str(document.line(cl)) if not firstIteration: # skip first iteration pos.setColumn( len(lineStr)) # set current column to the end of current line else: firstIteration = False # do nothing on first iteration # Iterate from the current column to a line start for cc in range(pos.column() - 1, -1, -1): #print("c: current position" + str(cl) + "," + str(cc) + ",ch='" + lineStr[cc] + "'") # Check open/close brackets if lineStr[ cc] == ')': # found closing char: append its position to the stack stack.append((cl, cc, False)) print("o( Add position: " + str(stack[-1])) continue if lineStr[cc] == '(': # found open char... if len( stack ): # if stack isn't empty (i.e. there are some closing chars met) print("o( Pop position: " + str(stack[-1])) nrl, nrc, isT = stack.pop( ) # remove last position from the stack if not isT: nestedRanges.append( # and append a nested range KTextEditor.Range(cl, cc, nrl, nrc)) else: raise LookupError("Misbalanced brackets: '(' @" + str(cl + 1) + ',' + str(cc + 1) + " and '>' @ " + str(nrl + 1) + ',' + str(nrc + 1)) else: # otherwise, openPos = (cl, cc + 1, False ) # remember range start (exclude an open char) print("o( Found position: " + str(openPos)) found = True break continue # Check for template angel brackets if lineStr[cc] == '>': if looksLikeTemplateAngelBracket(lineStr, cc): stack.append((cl, cc, True)) print("o< Add position: " + str(stack[-1])) else: print("o< Doesn't looks like template: " + str(cl) + "," + str(cc)) continue if lineStr[cc] == '<': if not looksLikeTemplateAngelBracket(lineStr, cc): print("o< Doesn't looks like template: " + str(cl) + "," + str(cc + 1)) elif len( stack ): # if stack isn't empty (i.e. there are some closing chars met) print("o< Pop position: " + str(stack[-1])) nrl, nrc, isT = stack.pop( ) # remove last position from the stack if isT: nestedRanges.append( # and append a nested range KTextEditor.Range(cl, cc, nrl, nrc)) else: raise LookupError("Misbalanced brackets: '<' @" + str(cl + 1) + ',' + str(cc + 1) + " and ')' @ " + str(nrl + 1) + ',' + str(nrc + 1)) raise LookupError("Misbalanced brackets") else: openPos = (cl, cc + 1, True ) # remember range start (exclude an open char) print("o< Found position: " + str(openPos)) found = True break continue if lineStr[cc] in breakChars and len(stack) == 0: breakPositions.append(KTextEditor.Cursor(cl, cc)) # Did we found smth on the current line? if found: break # Yep! Break the outer loop if not found: return (KTextEditor.Range(), list(), list() ) # Return empty ranges if nothing found assert (len(stack) == 0) # stack expected to be empty! breakPositions.reverse( ) # reverse breakers list required cuz we found 'em in a reverse order :) # Iterate from the current position towards the end of a document pos = view.cursorPosition() # get current cursor position again firstIteration = True found = False for cl in range(pos.line(), document.lines()): lineStr = str(document.line(cl)) if not firstIteration: # skip first iteration pos.setColumn(0) # set current column to the start of current line else: firstIteration = False # do nothing on first iteration for cc in range(pos.column(), len(lineStr)): #print("c: current position" + str(cl) + "," + str(cc) + ",ch='" + lineStr[cc] + "'") # Check open/close brackets if lineStr[cc] == '(': stack.append((cl, cc, False)) print("c) Add position: " + str(stack[-1])) continue if lineStr[cc] == ')': if len(stack): print("c) Pop position: " + str(stack[-1])) nrl, nrc, isT = stack.pop( ) # remove a last position from the stack if not isT: nestedRanges.append( # and append a nested range KTextEditor.Range(nrl, nrc, cl, cc)) else: raise LookupError("Misbalanced brackets: '<' @" + str(nrl + 1) + ',' + str(nrc + 1) + " and ')' @ " + str(cl + 1) + ',' + str(cc + 1)) else: closePos = (cl, cc, False) # remember the range end print("c) Found position: " + str(closePos)) found = True break continue # Check for template angel brackets if lineStr[cc] == '<': if looksLikeTemplateAngelBracket(lineStr, cc): stack.append((cl, cc, True)) print("c> Add position: " + str(stack[-1])) else: print("c> Doesn't looks like template: " + str(cl) + "," + str(cc)) continue if lineStr[cc] == '>': if not looksLikeTemplateAngelBracket(lineStr, cc): print("c> Doesn't looks like template: " + str(cl) + "," + str(cc)) elif len( stack ): # if stack isn't empty (i.e. there are some closing chars met) print("c> Pop position: " + str(stack[-1])) nrl, nrc, isT = stack.pop( ) # remove last position from the stack if isT: nestedRanges.append( # and append a nested range KTextEditor.Range(cl, cc, nrl, nrc)) else: raise LookupError("Misbalanced brackets: '(' @" + str(nrl + 1) + ',' + str(nrc + 1) + " and '>' @ " + str(cl + 1) + ',' + str(cc + 1)) else: closePos = (cl, cc, True) # remember the range end print("c> Found position: " + str(closePos)) found = True break continue if lineStr[cc] in breakChars and len(stack) == 0: breakPositions.append(KTextEditor.Cursor(cl, cc)) # Did we found smth on the current line? if found: break # Yep! Break the outer loop if not found: return (KTextEditor.Range(), list(), list() ) # Return empty ranges if nothing found assert (len(stack) == 0) # stack expected to be empty! if openPos[2] != closePos[2]: raise LookupError("Misbalanced brackets: at " + str(openPos[0] + 1) + ',' + str(openPos[1] + 1) + " and " + str(closePos[0] + 1) + ',' + str(closePos[1] + 1)) return (KTextEditor.Range(openPos[0], openPos[1], closePos[0], closePos[1]), nestedRanges, breakPositions)