def Set(self, stc, line, delete=False): """Add/Delete the marker to the stc at the given line @note: overrode to ensure only one is set in a buffer at a time """ super(ErrorMarker, self).Set(stc, line, delete) start = stc.GetLineEndPosition(max(line-1, 0)) end = stc.GetLineEndPosition(line) if start == end: start = 0 stc.Colourise(start, end) # Refresh for background marker
def __delitem__(self, key): stc = self.__STC if type(key) is IntType: stc.SetSelection(stc.PositionFromLine(key), stc.GetLineEndPosition(key) + 1) stc.ReplaceSelection('') elif type(key) is SliceType: stc.SetSelection(stc.PositionFromLine(key.start), stc.GetLineEndPosition(key.stop) + 1) stc.ReplaceSelection('') else: raise TypeError, _('%s not supported') % ` type(key) `
def AutoIndenter(stc, pos, ichar): """Auto indent python code. uses \n the text buffer will handle any eol character formatting. @param stc: EditraStyledTextCtrl @param pos: current carat position @param ichar: Indentation character @return: string """ rtxt = u'' line = stc.GetCurrentLine() spos = stc.PositionFromLine(line) text = stc.GetTextRange(spos, pos) epos = stc.GetLineEndPosition(line) inspace = text.isspace() # Cursor is in the indent area somewhere if inspace: return u"\n" + text # Check if the cursor is in column 0 and just return newline. if not len(text): return u"\n" # Ignore empty lines and backtrace to find the previous line that we can # get the indent position from # while text.isspace(): # line -= 1 # if line < 0: # return u'' # text = stc.GetTextRange(stc.PositionFromLine(line), pos) indent = stc.GetLineIndentation(line) if ichar == u"\t": tabw = stc.GetTabWidth() else: tabw = stc.GetIndent() i_space = indent / tabw end_spaces = ((indent - (tabw * i_space)) * u" ") tokens = filter(None, text.strip().split()) if tokens and not inspace: if tokens[-1].endswith(u":"): if tokens[0].rstrip(u":") in INDENT_KW: i_space += 1 elif tokens[-1].endswith(u"\\"): i_space += 1 elif tokens[0] in UNINDENT_KW: i_space = max(i_space - 1, 0) rval = u"\n" + (ichar * i_space) + end_spaces if inspace and ichar != u"\t": rpos = indent - (pos - spos) if rpos < len(rval) and rpos > 0: rval = rval[:-rpos] elif rpos >= len(rval): rval = u"\n" return rval
def coupleBalance(self, stc, ln, open, close): """Search the line to see if there are unmatched braces Search the line for unmatched braces given the open and close matching pair. This takes into account the style of the document to make sure that the brace isn't in a comment or string. @param stc: the StyledTextCtrl instance @param ln: line number @param open: the opening brace character, e.g. "(" @param close: the complimentary closing brace character, e.g. ")" @return: brace mismatch count: 0 for matching braces, positive for a surplus of opening braces, and negative for a surplus of closing braces """ if ln < 0: return 0 r = 0 fc = stc.GetLineIndentPosition(ln) lc = stc.GetLineEndPosition(ln) line = stc.GetStyledText(fc, lc) self.dprint(repr(line)) i = len(line) while i > 0: i -= 1 s = line[i] i -= 1 c = line[i] if c == open and not (stc.isStyleComment(s) and stc.isStyleString(s)): self.dprint("found %s at column %d" % (open, i/2)) r += 1 elif c == close and not (stc.isStyleComment(s) and stc.isStyleString(s)): self.dprint("found %s at column %d" % (close, i/2)) r -= 1 return r
def _GenRtf(self): """Generates the RTF equivalent of the displayed text in the current stc document window. @precondition: self._stc must have been set by a call to Generate @return: generated rtf marked up text """ # Buffer hasn't been set if self._stc is None: return u'' # Optimizations stc = self._stc def_fore = stc.GetDefaultForeColour(as_hex=True) self._colortbl.AddColor(def_fore) def_back = stc.GetDefaultBackColour(as_hex=True) self._colortbl.AddColor(def_back) last_pos = stc.GetLineEndPosition(stc.GetLineCount()) parse_pos = 0 last_id = None last_fore = None last_back = None start = end = 0 tmp_txt = list() font_tmp = "\\f0" fore_tmp = "\\cf%d" back_tmp = "\\cb%d" AddColor = self._colortbl.AddColor GetColorIndex = self._colortbl.GetColorIndex GetStyleAt = stc.GetStyleAt # Parse all characters/style bytes in document for parse_pos in xrange(last_pos + 1): sty_id = GetStyleAt(parse_pos) end = parse_pos # If style has changed build the previous section if sty_id != last_id: tag = stc.FindTagById(last_id) s_item = stc.GetItemByName(tag) AddColor(s_item.GetFore()) AddColor(s_item.GetBack()) tplate = font_tmp fid = GetColorIndex(s_item.GetFore()) if fid != last_fore: last_fore = fid tplate = tplate + (fore_tmp % fid) bid = GetColorIndex(s_item.GetBack()) if bid != last_back: last_back = bid tplate = tplate + (back_tmp % bid) tmp_txt.append(tplate + " " + \ self.TransformText(stc.GetTextRange(start, end))) start = end last_id = sty_id head = "{\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 %s;}}" % \ stc.GetDefaultFont().GetFaceName() return u"%s%s%s}" % (head, self._colortbl, "".join(tmp_txt))
def __setitem__(self, key, value): stc = self.__STC if type(key) is IntType: assert type(value) is StringType if key < len(self): stc.SetSelection(stc.PositionFromLine(key), stc.GetLineEndPosition(key)) stc.ReplaceSelection(value) else: raise IndexError elif type(key) is SliceType: lines = eols[stc.GetEOLMode()].join(value) stc.SetSelection(stc.PositionFromLine(key.start), stc.GetLineEndPosition(key.stop)) stc.ReplaceSelection(lines) else: raise TypeError, _('%s not supported') % ` type(key) `
def findIndent(self, stc, linenum): """Reindent the specified line to the correct level. Given a line, use some regex matching to determine the correct indent level. """ # The text begins at indpos; check some special cases to see if there # should be a dedent before = stc.GetLineIndentPosition(linenum) end = stc.GetLineEndPosition(linenum) cmd = stc.GetTextRange(before, end) dprint(cmd) # skip blank lines if len(cmd) > 0: # don't reindent comments if cmd[0] == "#": return stc.GetLineIndentation(linenum) match = self.commandre.match(cmd) if match: # it's a command, so it shouldn't be indented. self.dprint("It's a command! indent=0") return 0 # OK, not a command, so it depends on context. while linenum > 0: linenum -= 1 start = stc.PositionFromLine(linenum) style = stc.GetStyleAt(start) self.dprint("line=%d start=%d style=%d" % (linenum, start, style)) if style == 5 or style == 3: # OK, it's a possible command. end = stc.GetLineEndPosition(linenum) cmd = stc.GetTextRange(start, end) match = self.commandre.match(cmd) if match: # Yep, a command. This line should be tabbed self.dprint("tabbed!") return 8 return 0 elif style == 3: return 0 # If all else fails, just use the same amount of indentation as before return stc.GetLineIndentation(linenum)
def OnPrintPage(self, page): stc = self.stc self.stcLineHeight = stc.TextHeight(0) # calculate sizes including margin and scale dc = self.GetDC() dw, dh = dc.GetSizeTuple() mw = self.margin * dw mh = self.margin * dh textAreaHeight = dh - mh * 2 textAreaWidth = dw - mw * 2 scale = float(textAreaHeight) / (self.stcLineHeight * self.linesPerPage) dc.SetUserScale(scale, scale) # render page titles and numbers f = dc.GetFont() f.SetFamily(wx.ROMAN) f.SetFaceName('Times New Roman') f.SetPointSize(f.GetPointSize() + 3) dc.SetFont(f) if self.filename: tlw, tlh = dc.GetTextExtent(self.filename) dc.DrawText(self.filename, int(dw / scale / 2 - tlw / 2), int(mh / scale - tlh * 3)) if self.doPageNums: pageLabel = _('Page: %d') % page plw, plh = dc.GetTextExtent(pageLabel) dc.DrawText(pageLabel, int(dw / scale / 2 - plw / 2), int((textAreaHeight + mh) / scale + plh * 2)) # render stc into dc stcStartPos = stc.PositionFromLine((page - 1) * self.linesPerPage) stcEndPos = stc.GetLineEndPosition(page * self.linesPerPage - 1) maxWidth = 32000 stc.SetPrintColourMode(self.colourMode) ep = stc.FormatRange( 1, stcStartPos, stcEndPos, dc, dc, wx.Rect(int(mw / scale), int(mh / scale), maxWidth, int(textAreaHeight / scale)), wx.Rect(0, (page - 1) * self.linesPerPage * self.stcLineHeight, maxWidth, self.stcLineHeight * self.linesPerPage)) # warn when less characters than expected is rendered by the stc when # printing if not self.IsPreview(): if ep < stcEndPos: print _('warning: on page %s: not enough chars rendered, diff:' ) % (page, stcEndPos - ep) return True
def reindentComment(self, stc, ln, dedent_only): cursor = stc.GetCurrentPos() fc = stc.PositionFromLine(ln) lc = stc.GetLineEndPosition(ln) text = stc.GetTextRange(fc, lc) count = len(text) pos = 0 while pos < count and text[pos] == ' ': pos += 1 stc.SetTargetStart(fc) stc.SetTargetEnd(lc) stc.ReplaceTarget(text[pos:count]) cursor -= pos return cursor
def reindentSourceLine(self, stc, ln, dedent_only): cursor = stc.GetCurrentPos() fc = stc.PositionFromLine(ln) lc = stc.GetLineEndPosition(ln) text = stc.GetTextRange(fc, lc) #dprint("1234567") #dprint(text) #col = stc.GetColumn(cursor) #dprint(" "*col + "_") if len(text) > 5: numbers = text[0:5] continuation = text[5] remainder = text[6:] else: numbers = text + " "*(5 - len(text)) continuation = " " remainder = "" newind = self.findIndent(stc, ln) - 6 if newind < 0: newind = 0 #dprint("newind: %d" % newind) col = stc.GetColumn(cursor) if col < 5: cursor = fc + 6 + newind elif col > 5: before = len(remainder) remainder = remainder.lstrip() leading_blanks = before - len(remainder) #dprint("leading blanks: %d" % leading_blanks) if leading_blanks > newind: cursor -= leading_blanks - newind elif col - 6 <= newind: cursor = fc + 6 + newind remainder = remainder.lstrip() remainder = " "*newind + remainder numbers = numbers.strip() line = "%-5s%s%s" % (numbers, continuation, remainder) #dprint("1234567") #dprint(line) #col = stc.GetColumn(cursor) #dprint(" "*col + "_") stc.SetTargetStart(fc) stc.SetTargetEnd(lc) stc.ReplaceTarget(line) return cursor
def getPreviousText(self, stc, linenum): """Find the text above the line with the same fold level. """ fold = self.getFold(stc, linenum) ln = linenum - 1 fc = lc = stc.GetLineEndPosition(ln) above = '' while ln > 0: f = self.getFold(stc, ln) if f != fold: above = stc.GetTextRange(fc, lc) break fc = stc.PositionFromLine(ln) ln -= 1 return above
def getCodeChars(self, stc, ln, lc=-1): """Get a version of the given line with all non code chars blanked out. This function blanks out all non-code characters (comments, strings, etc) from the line and returns a copy of the interesting stuff. @param stc: stc of interest @param ln: line number @param lc: optional integer specifying the last position on the line to consider """ fc = stc.PositionFromLine(ln) if lc < 0: lc = stc.GetLineEndPosition(ln) if lc < fc: # FIXME: fail hard during development raise IndexError("bad line specification for line %d: fc=%d lc=%d" % (ln, fc, lc)) mask = (2 ** stc.GetStyleBits()) - 1 out = [] line = stc.GetStyledText(fc, lc) self.dprint(repr(line)) i = len(line) # replace all uninteresting chars with blanks skip = self.getNonCodeStyles(stc) while i > 0: i -= 1 s = ord(line[i]) & mask i -= 1 if s in skip: c = ' ' else: c = line[i] out.append(c) # Note that we assembled the string in reverse, so flip it around out = ''.join(reversed(out)) return out
def electricChar(self, stc, uchar): i = ord(uchar) if i >= ord('0') and i <= ord('9'): pos = stc.GetCurrentPos() s = stc.GetStyleAt(pos) col = stc.GetColumn(pos) if not stc.isStyleComment(s) and col < 5: ln = stc.LineFromPosition(pos) fc = stc.PositionFromLine(ln) lc = stc.GetLineEndPosition(ln) text = stc.GetTextRange(fc, lc) #dprint("pos=%d col=%d ln=%d fc=%d lc=%d len=%d" % (pos, col, ln, fc, lc, len(text))) #dprint("1234567") #dprint(text) if len(text) > 5: numbers = text[0:5] continuation = text[5] remainder = text[6:] else: numbers = text + " "*(5 - len(text)) continuation = " " remainder = "" numbers = numbers[0:col] + uchar + numbers[col:] before = len(numbers) numbers = numbers.lstrip() if before > len(numbers): col -= before - len(numbers) numbers = numbers.strip() #dprint("pos=%d col=%d ln=%d fc=%d lc=%d len=%d" % (pos, col, ln, fc, lc, len(text))) line = "%-5s%s%s" % (numbers, continuation, remainder) #dprint("1234567") #dprint(line) stc.SetTargetStart(fc) stc.SetTargetEnd(lc) stc.ReplaceTarget(line) stc.GotoPos(fc + col + 1) return True return False
def StyleText(self, evt): """Handle the EVT_STC_STYLENEEDED event.""" stc = evt.GetEventObject() last_styled_pos = stc.GetEndStyled() line = stc.LineFromPosition(last_styled_pos) start_pos = stc.PositionFromLine(line) end_pos = evt.GetPosition() while start_pos < end_pos: stc.StartStyling(start_pos, 0x1f) curchar = chr(stc.GetCharAt(start_pos)) if curchar in self.alpha: start = stc.WordStartPosition(start_pos, True) end = start + 1 while chr(stc.GetCharAt(end)) != " " and end < stc.GetLength(): end += 1 word = stc.GetTextRange(start, end) if word in self.keywords: style = self.STC_EXPR_KEYWORD stc.SetStyling(len(word), style) elif word in self.keywords2: style = self.STC_EXPR_KEYWORD2 stc.SetStyling(len(word), style) else: style = self.STC_EXPR_DEFAULT stc.SetStyling(len(word), style) start_pos += len(word) elif curchar == ";": eol = stc.GetLineEndPosition(stc.LineFromPosition(start_pos)) style = self.STC_EXPR_COMMENT stc.SetStyling(eol - start_pos, style) start_pos = eol else: style = self.STC_EXPR_DEFAULT stc.SetStyling(1, style) start_pos += 1
def findIndent(self, stc, linenum): """Find proper indentation of the current Python source line. This uses IDLE's python parsing routine to find the indent level of the specified line based on the python code that comes before it. @param linenum: line number @return: integer indicating number of columns to indent. """ indentwidth = stc.GetIndent() tabwidth = stc.GetTabWidth() indent = stc.GetLineIndentation(linenum) y = PyParse.Parser(indentwidth, tabwidth) # FIXME: context line hack straight from IDLE for context in [50, 500, 5000000]: firstline = linenum - context if firstline < 0: firstline = 0 start = stc.PositionFromLine(firstline) # end is the position before the first character of the line, so # we're looking at the code up to the start of the current line. end = stc.PositionFromLine(linenum) rawtext = stc.GetTextRange(start, end) # Handle two issues with this loop. 1: Remove comments that start # at column zero so they don't affect the indenting. 2: PyParse # is hardcoded for "\n" style newlines only, so by splitting the # lines here we can change whatever the newlines are into "\n" # characters. lines = [] for line in rawtext.splitlines(): if len(line) > 0: if line[0] != '#': lines.append(line) lines.append('') # include a blank line at the end rawtext = "\n".join(lines) y.set_str(rawtext) bod = y.find_good_parse_start(build_char_in_string_func( stc, start)) if bod is not None or firstline == 0: break #dprint(rawtext) self.dprint("bod = %s" % bod) y.set_lo(bod or 0) c = y.get_continuation_type() self.dprint("continuation type: %s" % c) extra_data = None if c != PyParse.C_NONE: # The current stmt hasn't ended yet. if c == PyParse.C_STRING_FIRST_LINE: s = stc.GetStyleAt(end) if s == 6 or s == 7: # Inside a triple quoted string (TQS) self.dprint("C_STRING_FIRST_LINE in TQS") indentstr = y.get_base_indent_string() indent = len(indentstr.expandtabs(tabwidth)) else: # after the first line of a string; do not indent at all self.dprint("C_STRING_FIRST_LINE") pass elif c == PyParse.C_STRING_NEXT_LINES: # inside a string which started before this line; # just mimic the current indent #text.insert("insert", indent) s = stc.GetStyleAt(end) if s == 6 or s == 7: # Inside a triple quoted string (TQS) self.dprint("C_STRING_NEXT_LINES in TQS") indentstr = y.get_base_indent_string() indent = len(indentstr.expandtabs(tabwidth)) else: # FIXME: Does this ever happen without being in a TQS??? self.dprint("C_STRING_NEXT_LINES") elif c == PyParse.C_BRACKET: # line up with the first (if any) element of the # last open bracket structure; else indent one # level beyond the indent of the line with the # last open bracket self.dprint("C_BRACKET") #self.reindent_to(y.compute_bracket_indent()) indent = y.compute_bracket_indent() elif c == PyParse.C_BACKSLASH: # if more than one line in this stmt already, just # mimic the current indent; else if initial line # has a start on an assignment stmt, indent to # beyond leftmost =; else to beyond first chunk of # non-whitespace on initial line if y.get_num_lines_in_stmt() > 1: pass else: indent = y.compute_backslash_indent() else: assert 0, "bogus continuation type %r" % (c, ) else: # This line starts a brand new stmt; indent relative to # indentation of initial line of closest preceding # interesting stmt. indentstr = y.get_base_indent_string() indent = len(indentstr.expandtabs(tabwidth)) if y.is_block_opener(): self.dprint("block opener") indent += indentwidth extra_data = "block opener" elif indent and y.is_block_closer(): self.dprint("block dedent") indent = ((indent - 1) // indentwidth) * indentwidth extra_data = "block dedent" self.dprint("indent = %d" % indent) # check some special cases to see if they line should be dedented by # a level before = stc.GetLineIndentPosition(linenum) style = stc.GetStyleAt(before) end = stc.GetLineEndPosition(linenum) cmd = stc.GetTextRange(before, end) #dprint("checking %s" % cmd) if linenum > 0 and style == wx.stc.STC_P_WORD and ( cmd.startswith('else') or cmd.startswith('elif') or cmd.startswith('except') or cmd.startswith('finally')): self.dprint("Found a dedent: %s" % cmd) if extra_data != "block dedent": # If we aren't right after a return or something that already # caused a dedent, dedent it indent -= indentwidth return indent
def AutoIndenter(stc, current_pos, indent_char): """ Determines the indentation rule. 0) If the line is empty except for whitespace, indent to the current column. 1) If all strings and parenthesis are closed, then indent based on how this line has been styled. 2) If we're in a string, indent to one more than the position at which the string started. 3) If we've opened more parenthesis then we closed on the current line, indent by 1. 4) If we've closed more parenthesis than we opened on the current line, dedent by 1. 5) Otherwise, keep the current indentation. """ line = stc.LineFromPosition(current_pos) while line >= 0: line_state = stc.GetLineState(line) if line_state: break line -= 1 start = stc.PositionFromLine(line) text = stc.GetTextRangeUTF8(start, current_pos) pos = -1 len_text = len(text) # States for the indenting state machine. ISTATE_INDENT = 0 # Line indentation. ISTATE_CODE = 1 # Normal code. ISTATE_COMMENT = 2 # Comment. ISTATE_COMMENT_LINE = 3 # Full-line comment. ISTATE_STRING = 4 # In a string. ISTATE_EOL = 5 # At the end of the line. state = ISTATE_EOL # The indentation of the last non-blank, non-comment line. prior_indent = 0 # The number of parens that are open in the statement. open_parens = 0 # The net change in parens on the current line. net_parens = 0 # Where the current line started. line_start = 0 # The quote characters used to close the current string. quote_chars = None # The indentation to use if we're in a quote. quote_indent = 0 while pos + 1 < len_text: pos += 1 c = text[pos] if state == ISTATE_EOL: line_start = pos net_parens = 0 state = ISTATE_INDENT if state == ISTATE_INDENT: if c == " ": continue elif c == "\n": state = ISTATE_EOL continue elif c == "#": state = ISTATE_COMMENT_LINE continue state = ISTATE_CODE prior_indent = pos - line_start # Intentionally fall through. if state == ISTATE_COMMENT or state == ISTATE_COMMENT_LINE: if c == "\n": state = ISTATE_EOL continue continue elif state == ISTATE_CODE: if c == "\n": state = ISTATE_EOL continue if c in "\"'`": start = text[pos:pos + 3] if start == "'''" or start == '"""': quote_chars = start quote_indent = pos - line_start pos += 2 state = ISTATE_STRING continue quote_chars = c quote_indent = 1 + pos - line_start state = ISTATE_STRING continue if c in "([{": net_parens += 1 open_parens += 1 continue if c in ")]}": net_parens -= 1 open_parens -= 1 continue if c == "#": state = ISTATE_COMMENT continue continue elif state == ISTATE_STRING: if c == "\n": line_start = pos + 1 continue if c == "\\": pos += 1 continue if c == quote_chars: state = ISTATE_CODE continue if text[pos:pos + 3] == quote_chars: pos += 2 state = ISTATE_CODE continue continue # Compute the indent of the line itself. INDENTWIDTH = profiler.Profile_Get("INDENTWIDTH") line_indent = line_state & INDENT_MASK if state == ISTATE_STRING: indent = quote_indent elif state == ISTATE_COMMENT_LINE: l = stc.GetCurrentLine() indent = stc.GetLineIndentation(l) elif open_parens <= 0: if state == ISTATE_INDENT or state == ISTATE_EOL: l = stc.GetCurrentLine() if stc.GetLineIndentPosition(l) == stc.GetLineEndPosition(l): indent = stc.GetColumn(current_pos) else: indent = line_indent elif line_state & INDENTS: indent = line_indent + INDENTWIDTH else: indent = line_indent elif net_parens > 0: indent = prior_indent + INDENTWIDTH elif net_parens < 0: indent = max(line_indent + INDENTWIDTH, prior_indent - INDENTWIDTH) else: indent = prior_indent # Implement the indent. eolch = stc.GetEOLChar() stc.AddText(eolch) l = stc.GetCurrentLine() stc.SetLineIndentation(l, indent) stc.GotoPos(stc.GetLineIndentPosition(l))
def findIndent(self, stc, linenum): ln = linenum pos = 0 fc = stc.PositionFromLine(ln) s = stc.GetStyleAt(fc) if stc.isStyleComment(s): # comment lines aren't indented at all in F77 return pos # find the first line with content that is not starting with comment # text, and take the base indent position from that above = '' while ln > 0: ln -= 1 fc = stc.PositionFromLine(ln) s = stc.GetStyleAt(fc) if stc.isStyleComment(s): continue lc = stc.GetLineEndPosition(ln) text = stc.GetTextRange(fc, lc) if len(text) < 6: # skip lines that have only labels continue if text[5] != ' ': # skip continuation lines continue above = text[6:] pos = 6 while pos < len(text) and text[pos] == ' ': pos += 1 break # try 'couples' for an opening on the above line first. since we only # adjust by 1 unit, we only need 1 match. adjustment = 0 if '(' in self.couples and self.coupleBalance(stc, ln, '(', ')') > 0: adjustment += 1 # check if we should indent, unless the line starts with comment text, # or the match is in comment text Here we look at the line above the # current line if self.reIndentAfter: match = self.reIndentAfter.search(above) self.dprint(above) if match: self.dprint("reIndentAfter: found %s at %d" % (match.group(0), match.start(0))) adjustment += 1 # Now that we've found the base indent based on the lines above the # current line, check if the current line needs to be modified fc = stc.PositionFromLine(linenum) lc = stc.GetLineEndPosition(linenum) text = stc.GetTextRange(fc, lc) if len(text) > 6: # Note that no lines that I know of automatically indent # themselves, so we only check for lines that should unindent # themselves like ELSE, ENDIF, etc. text = text[6:] if self.reUnindent: match = self.reUnindent.search(text) if match: self.dprint("reUnndent: found %s at %d" % (match.group(0), match.start(0))) adjustment -= 1 # Find the actual number of spaces to indent if adjustment > 0: pos += stc.GetIndent() elif adjustment < 0: pos -= stc.GetIndent() if pos < 0: pos = 0 return pos
def StyleText(stc, start, end): """Style the text @param stc: Styled text control instance @param start: Start position @param end: end position """ max_styled_line = max_line_cache.get(id(stc), 0) # Set up the bad indentation indicator style. if stc.IndicatorGetStyle(1) != wx.stc.STC_INDIC_SQUIGGLE: stc.IndicatorSetStyle(1, wx.stc.STC_INDIC_SQUIGGLE) stc.IndicatorSetForeground(1, "#FF0000") # A change to one line can change others below it. So we restyle all # visible text with any change. (We restyle a bit more so we don't get # caught in things like triple-quoted strings.) try: vis_end_line = stc.GetLastVisibleLine() + 20 except: # Fails if we're in the preview. vis_end_line = stc.GetLineCount() vis_end_pos = stc.GetLineEndPosition(vis_end_line) end = max(end, vis_end_pos) # First, figure out the line based on the position. line = stc.LineFromPosition(start) # Jump back a bunch of lines, so that we always restyle the entire # screen. line = min(line - 60, max_styled_line) if line < 0: line = 0 # Find the start of the line that's been styled before this one. while line and stc.GetLineState(line) == 0: line -= 1 # The indentation starting the current block. (None to indicate # it hasn't been set yet.) block_indent = 0 # The type of block we're dealing with. block_type = BLOCK_RPY # Is this block's indentation optional? block_maybe_indents = False # A stack of outer blocks, giving the indent and type of those # blocks. block_stack = [] # Find the last line before line with a 0 indent. (Or the first # line if we don't have one line that.) base_line = line while base_line > 0: base_line -= 1 state = stc.GetLineState(base_line) if not state: continue indent = state & INDENT_MASK if indent == 0: break if base_line < 0: base_line = 0 # Figure out what sort of block we're in, and build up the stack # of non-closed blocks. for i in range(base_line, line): state = stc.GetLineState(i) if not state: continue indent = state & INDENT_MASK if block_indent is None: block_indent = indent if state & INDENTS: block_stack.append((block_indent, block_type)) block_indent = None block_type = state & BLOCK_MASK if state & MAYBE_INDENTS: block_maybe_indents = True else: block_maybe_indents = False while indent < block_indent: block_indent, block_type = block_stack.pop() # Clean out the old (no longer relevant) line states. for i in range(line, max_styled_line + 1): stc.SetLineState(i, 0) new_start = stc.PositionFromLine(line) text = stc.GetTextRangeUTF8(new_start, end) # End open strings. text += "\n\"\"\"'''" len_text = len(text) pos = 0 # stc.StartStyling(new_start, 0xff & (~wx.stc.STC_INDIC2_MASK)) sb = StyleBuffer(stc, new_start, 0xff & (~wx.stc.STC_INDIC2_MASK)) while pos < len_text: # The line and pos this statement begins on. statement_line = line m = line_start_re.match(text, pos) pos = m.end() indent = len(m.group('indent')) comment = m.group('comment') eol = m.group('eol') if eol: sb.style(indent, STC_RENPY_DEFAULT) # Style a line-long comment. if comment: sb.style(len(comment), STC_RENPY_COMMENT) # If the line is empty, continue. if eol: sb.style(len(eol), STC_RENPY_DEFAULT) line += 1 continue # Otherwise, we have a real line. Figure out the indentation of it. indent_indicator = 0 # If we're indented from the previous line and starting a new block, # deal with that. if block_indent is None and indent > block_stack[-1][0]: block_indent = indent # Deal with empty blocks. Not an error, because of label. if block_indent is None: if INDENT_ERRORS and not block_maybe_indents: indent_indicator = wx.stc.STC_INDIC1_MASK block_indent, block_type = block_stack.pop() # We outdented, go out a block or more. while block_indent > indent: block_indent, block_type = block_stack.pop() # Now check that we match the current block. if INDENT_ERRORS and indent != block_indent: # Indentation error. indent_indicator = wx.stc.STC_INDIC1_MASK # Style the indentation. sb.style(indent, STC_RENPY_DEFAULT | indent_indicator) # Store the line type. line_type = block_type >> 4 line_text = "" paren_depth = 0 while True: m = regex.match(text, pos) if not m: break pos = m.end() if pos > len_text: pos = len_text # Rules for applying styling. string = m.group("string") if string: line_text += string line += string.count("\n") i = 0 while string[i] in 'ur': i += 1 sb.style(i + 1, STC_RENPY_DEFAULT) sb.style(len(string) - 2 - i, STC_RENPY_STRING) sb.style(1, STC_RENPY_DEFAULT) continue word = m.group("word") if word: line_text += word style = STC_RENPY_DEFAULT if line_type == LINE_RPY: if word in RENPY_KEYWORDS: style = STC_RENPY_KEYWORD elif word in RENPY_PROPERTIES: style = STC_RENPY_KEYWORD2 elif line_type == LINE_PY: if word in PYTHON_KEYWORDS: style = STC_RENPY_KEYWORD sb.style(len(word), style) continue comment = m.group("comment") if comment: # Don't include comment text in line_text. sb.style(len(comment), STC_RENPY_COMMENT) continue # Style everything else. line_text += m.group(0) sb.style(len(m.group(0)), STC_RENPY_DEFAULT) # Rules for everything else. if m.group("open_paren"): paren_depth += 1 elif m.group("close_paren"): paren_depth -= 1 elif m.group("eol"): line += 1 if not paren_depth: break # End a runaway line, eventually. if len(line_text) > 8000: break line_text = line_text.strip() block_maybe_indents = False if line_text and line_text[-1] == ':': block_stack.append((block_indent, block_type)) block_indent = None indents = INDENTS if line_text.startswith("label"): indents |= MAYBE_INDENTS block_maybe_indents = True if line_type == LINE_RPY: block_type = BLOCK_RPY if renpy_python.match(line_text): block_type = BLOCK_PY else: block_type = BLOCK_PY else: indents = 0 new_state = indent | line_type | block_type | indents stc.SetLineState(statement_line, new_state) sb.apply() max_line_cache[id(stc)] = line
def findIndent(self, stc, linenum): """Determine the correct indentation for the line. This routine uses regular expressions to determine the indentation level of the line. @param linenum: current line number @param return: the number of columns to indent, or None to leave as-is """ if linenum < 1: return None #// find the first line with content that is not starting with comment text, #// and take the position from that ln = linenum pos = 0 above = '' while ln > 0: ln -= 1 fc = stc.GetLineIndentPosition(ln) lc = stc.GetLineEndPosition(ln) self.dprint("ln=%d fc=%d lc=%d line=-->%s<--" % (ln, fc, lc, stc.GetLine(ln))) # skip blank lines if fc < lc: s = stc.GetStyleAt(fc) if stc.isStyleComment(s): continue pos = stc.GetLineIndentation(ln) above = stc.GetTextRange(fc, lc) break # // try 'couples' for an opening on the above line first. since we only adjust by 1 unit, # // we only need 1 match. adjustment = 0 if '(' in self.couples and self.coupleBalance(stc, ln, '(', ')') > 0: adjustment += 1 elif '[' in self.couples and self.coupleBalance(stc, ln, '[', ']') > 0: adjustment += 1 elif '{' in self.couples and self.coupleBalance(stc, ln, '{', '}') > 0: adjustment += 1 # // Try 'couples' for a closing on this line first. since we only adjust by 1 unit, # // we only need 1 match. For unindenting, we look for a closing character # // *at the beginning of the line* # // NOTE Assume that a closing brace with the configured attribute on the start # // of the line is closing. # // When acting on processChar, the character isn't highlighted. So I could # // either not check, assuming that the first char *is* meant to close, or do a # // match test if the attrib is 0. How ever, doing that is # // a potentially huge job, if the match is several hundred lines away. # // Currently, the check is done. # { # KateTextLine::Ptr tl = doc->plainKateTextLine( line.line() ); # int i = tl->firstChar(); # if ( i > -1 ) # { # QChar ch = tl->getChar( i ); # uchar at = tl->attribute( i ); # kdDebug(13030)<<"attrib is "<<at<<endl; # if ( d->couples & Parens && ch == ')' # && ( at == d->coupleAttrib # || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) # ) # ) # adjustment--; # else if ( d->couples & Braces && ch == '}' # && ( at == d->coupleAttrib # || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) # ) # ) # adjustment--; # else if ( d->couples & Brackets && ch == ']' # && ( at == d->coupleAttrib # || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) # ) # ) # adjustment--; # } # } # Haven't figured out what that was for, so ignoring for now. # // check if we should indent, unless the line starts with comment text, # // or the match is in comment text # Here we look at the line above the current line if self.reIndentAfter: match = self.reIndentAfter.search(above) self.dprint(above) if match: self.dprint("reIndentAfter: found %s at %d" % (match.group(0), match.start(0))) adjustment += 1 # // else, check if this line should indent unless ... # ktl = doc->plainKateTextLine( line.line() ); # if ( ! d->reIndent.isEmpty() # && (matchpos = d->reIndent.search( doc->textLine( line.line() ) )) > -1 # && ! ISCOMMENT ) # adjustment++; fc = stc.GetLineIndentPosition(linenum) lc = stc.GetLineEndPosition(linenum) s = stc.GetStyleAt(fc) if self.reIndent and lc > fc and not stc.isStyleComment(s): text = stc.GetTextRange(fc, lc) match = self.reIndent.search(text) self.dprint(text) if match: self.dprint("reIndent: found %s at %d" % (match.group(0), match.start(0))) adjustment += 1 # // else, check if the current line indicates if we should remove indentation unless ... if self.reUnindent and lc > fc and not stc.isStyleComment(s): text = stc.GetTextRange(fc, lc) match = self.reUnindent.search(text) if match: self.dprint("reUnndent: found %s at %d" % (match.group(0), match.start(0))) adjustment -= 1 # Find the actual number of spaces to indent if adjustment > 0: pos += stc.GetIndent() elif adjustment < 0: pos -= stc.GetIndent() if pos < 0: pos = 0 return pos