Skip to content

Commit

Permalink
Merge pull request #274 from oracc/error-highlight-243
Browse files Browse the repository at this point in the history
Error highlight 243
  • Loading branch information
sgrieve authored Aug 23, 2017
2 parents d049539 + b9fa41b commit 96d13ad
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 6 deletions.
91 changes: 91 additions & 0 deletions python/nammu/controller/AtfAreaController.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ def get_viewport_top_bottom(self, top, bottom):
return top_line, bottom_line

def pad_top_viewport_caret(self, top_left_char, text):
'''
Extend the top of the viewport to the nearest header, so we don't
have problems with malformed atf files being highlighted.
'''

# Test that there is text in the edit area
if len(text) == 0:
Expand Down Expand Up @@ -198,6 +202,10 @@ def pad_top_viewport_caret(self, top_left_char, text):
return top_left_char

def pad_bottom_viewport_caret(self, bottom_left_char, text):
'''
Adds two lines to the bottom of the viewport so we dont have any
unhighlighted lines visible.
'''

# Test that there is text in the edit area
if len(text) == 0:
Expand All @@ -219,6 +227,66 @@ def pad_bottom_viewport_caret(self, bottom_left_char, text):

return bottom_left_char

def update_error_lines(self, caret_line, no_of_lines, flag):
'''
Given a caret line number, a number of lines and a flag indicating
whether the error lines need incremented ('insert') or decremented
('remove'). Update the line numbers of the keys in the dictionary
self.validation_errors so that error highlighting follows broken lines
during editing.
'''

# If the supplied edit does not add or remove any lines, do nothing
if no_of_lines < 1:
return

error_lines = self.validation_errors.keys()

# For legacy reasons the keys are strings, but we need ints
e_lines_int = [int(a) for a in error_lines]

# We only care about edits hapenning above error lines
if caret_line > max(e_lines_int):
return

# We need the line end position and the caret position
positions = self.getLinePositions(self.view.oldtext)
caret_pos = self.edit_area.getCaretPosition()
line_end = positions[caret_line - 1][1]

tmp = {}
for q, err in enumerate(e_lines_int):
# We are above an error line or on an error line but not at its end
if (err > caret_line) or (err == caret_line and
caret_pos != line_end):
fixed_line_no = self.line_fix(e_lines_int[q], no_of_lines,
flag)

# rebuild self.controller.validation_errors
tmp[str(fixed_line_no)] = self.validation_errors[str(err)]

# We are below or after the error lines so do nothing
else:
tmp[str(err)] = self.validation_errors[str(err)]

# Write the updated line numbers to the error dictionary
self.validation_errors = tmp

def line_fix(self, e_line_no, no_of_lines, flag):
'''
Helper function containing the logic for incrementing or decrementing
line numbers
'''
if flag == 'insert':
e_line_no += no_of_lines
elif flag == 'remove':
e_line_no -= no_of_lines
# handle potential out of bounds - 1 is top of the file
if e_line_no < 1:
e_line_no = 1

return e_line_no

def syntax_highlight(self, top_caret=None, bottom_caret=None):
'''
Short hand for syntax highlighting. Takes the line bounds.
Expand Down Expand Up @@ -265,3 +333,26 @@ def getPositionFromLine(self, text, line_num):
pos = 0

return pos

def getLinePositions(self, text):
'''
Given a block of text, return the caret positions
at the start and end of each line as a list of tuples in the order
(start, end) assuming left to right text.
The hacky list addition is to handle off by one errors as the 1st line
starts at position 0, whereas every other line starts at +1 past the
end of the last line and we also need to add in the final line length
manually.
'''
if len(text) > 0:
compiled = re.compile(r"\n")
textiter = compiled.finditer(text)
pos = [m.start() for m in textiter]
else:
return [(0, 0)]

# Build lists of the starts and ends of each line
starts = [0] + [x + 1 for x in pos]
ends = pos + [len(text)]

return zip(starts, ends)
72 changes: 70 additions & 2 deletions python/nammu/view/AtfAreaView.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from javax.swing.text import StyleContext, StyleConstants
from javax.swing.text import SimpleAttributeSet
from javax.swing.undo import UndoManager, CompoundEdit
from javax.swing.event import UndoableEditListener
from javax.swing.event import UndoableEditListener, DocumentListener
from contextlib import contextmanager
from .AtfEditArea import AtfEditArea

Expand Down Expand Up @@ -78,6 +78,15 @@ def __init__(self, controller):
# Also needed in secondary area:
self.secondary_area.addKeyListener(AtfAreaKeyListener(self))

# Add a document listener to track changes to files
docListener = atfAreaDocumentListener(self)
self.edit_area.getDocument().addDocumentListener(docListener)

# instance variable to store a record of the text contents prior to the
# most recent change. Needed so that the different listeners can access
# this to handle error line updating.
self.oldtext = ''

def toggle_split(self, split_orientation=None):
'''
Clear ATF edit area and repaint chosen layout (splitscreen/scrollpane).
Expand Down Expand Up @@ -145,6 +154,62 @@ def get_viewport_carets(self):
return top_ch, bottom_ch


class atfAreaDocumentListener(DocumentListener):
def __init__(self, areaview):
self.areaviewcontroller = areaview.controller
self.areaview = areaview

def errorUpdate(self, e, text, flag):
'''
Method to handle the updating of error lines.
flag indicates whether the error lines need incremented ('insert')
or decrmented ('remove').
'''

# Only need to do this if we have error_lines
if self.areaviewcontroller.validation_errors == {}:
return

# Gets the position and length of the edit to the document
length = e.getLength()
offset = e.getOffset()

# Slice out the edited text
edited = text[offset:length + offset]

if '\n' in edited:
no_of_newlines = edited.count('\n')

# Get the line no of the caret postion
caret_line = self.areaviewcontroller.edit_area.get_line_num(offset)

# Call our error line update method here, passing no_of_newlines
self.areaviewcontroller.update_error_lines(caret_line,
no_of_newlines,
flag)

def changedUpdate(self, e):
'''
Must be implemented to avoid NotImplemented errors
'''
pass

def insertUpdate(self, e):
'''
Listen for an insertion to the document.
'''
text = self.areaviewcontroller.edit_area.getText()
self.errorUpdate(e, text, 'insert')

def removeUpdate(self, e):
'''
Listen for a removal from the document
'''
# Get the text prior to this edit event
text = self.areaview.oldtext
self.errorUpdate(e, text, 'remove')


class atfAreaAdjustmentListener(AdjustmentListener):
def __init__(self, areaview):
self.areaviewcontroller = areaview.controller
Expand Down Expand Up @@ -182,7 +247,10 @@ def keyReleased(self, ke):
# We have to implement these since the baseclass versions
# raise non implemented errors when called by the event.
def keyPressed(self, ke):
pass
# Set the oldtext parameter, which stores the contents of the textfield
# prior to the edits triggered by the keypress event. Needed for
# tracking error highlighting on removal of lines.
self.areaview.oldtext = self.areaviewcontroller.edit_area.getText()

def keyTyped(self, ke):
# It would be more natural to use this event. However
Expand Down
4 changes: 0 additions & 4 deletions python/nammu/view/AtfEditArea.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,31 +90,27 @@ def replaceSelection(self, text):
after replacing some text.
'''
super(AtfEditArea, self).replaceSelection(text)
self.controller.syntax_highlight()

def cut(self):
'''
Override JTextPane's cut to call syntax highlighting so that it still
works when user cuts text via toolbar button or mouse.
'''
super(AtfEditArea, self).cut()
self.controller.syntax_highlight()

def copy(self):
'''
Override JTextPane's copy to call syntax highlighting so that it still
works when user copies text via toolbar button or mouse.
'''
super(AtfEditArea, self).copy()
self.controller.syntax_highlight()

def paste(self):
'''
Override JTextPane's paste to call syntax highlighting so that it still
works when user pastes text via toolbar button or mouse.
'''
super(AtfEditArea, self).paste()
self.controller.syntax_highlight()


class CustomMouseListener(MouseAdapter):
Expand Down

0 comments on commit 96d13ad

Please sign in to comment.