Adding support for folding/unfolding and auto-indentation in ST code editor
authorlaurent
Mon, 24 Oct 2011 02:06:34 +0200
changeset 582 aa41547baa2a
parent 581 c2d96ea9c14a
child 583 3f3f9b25ff9f
Adding support for folding/unfolding and auto-indentation in ST code editor
TextViewer.py
plcopen/structures.py
--- a/TextViewer.py	Mon Oct 24 02:03:42 2011 +0200
+++ b/TextViewer.py	Mon Oct 24 02:06:34 2011 +0200
@@ -29,6 +29,7 @@
 import re
 
 from graphics.GraphicCommons import ERROR_HIGHLIGHT, SEARCH_RESULT_HIGHLIGHT, REFRESH_HIGHLIGHT_PERIOD
+from plcopen.structures import ST_BLOCK_START_KEYWORDS, ST_BLOCK_END_KEYWORDS
 
 #-------------------------------------------------------------------------------
 #                         Textual programs Viewer class
@@ -98,6 +99,8 @@
     else:
         return None
 
+def LineStartswith(line, symbols):
+    return reduce(lambda x, y: x or y, map(lambda x: line.startswith(x), symbols), False)
 
 class TextViewer(wx.stc.StyledTextCtrl):
     
@@ -148,6 +151,15 @@
         self.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
         self.SetMarginWidth(1, 50)
         
+        # Folding
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPEN,    wx.stc.STC_MARK_BOXMINUS,          "white", "#808080")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDER,        wx.stc.STC_MARK_BOXPLUS,           "white", "#808080")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERSUB,     wx.stc.STC_MARK_VLINE,             "white", "#808080")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERTAIL,    wx.stc.STC_MARK_LCORNER,           "white", "#808080")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEREND,     wx.stc.STC_MARK_BOXPLUSCONNECTED,  "white", "#808080")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_TCORNER,           "white", "#808080")
+        
         # Indentation size
         self.SetTabWidth(2)
         self.SetUseTabs(0)
@@ -179,6 +191,7 @@
                              wx.stc.STC_PERFORMED_USER)
 
         self.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyleNeeded, id=ID_TEXTVIEWER)
+        self.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
         if controler:
             self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
             self.Bind(wx.stc.EVT_STC_DO_DROP, self.OnDoDrop, id=ID_TEXTVIEWER)
@@ -300,6 +313,11 @@
     
     def SetTextSyntax(self, syntax):
         self.TextSyntax = syntax
+        if syntax == "ST":
+            self.SetMarginType(2, wx.stc.STC_MARGIN_SYMBOL)
+            self.SetMarginMask(2, wx.stc.STC_MASK_FOLDERS)
+            self.SetMarginSensitive(2, 1)
+            self.SetMarginWidth(2, 12)
     
     def SetKeywords(self, keywords):
         self.Keywords = [keyword.upper() for keyword in keywords]
@@ -368,7 +386,7 @@
                         self.Functions[blockname] = {"interface": interface,
                                                      "extensible": blocktype["extensible"]}
         self.Colourise(0, -1)
-    
+            
     def RefreshVariableTree(self):
         words = self.TagName.split("::")
         self.Variables = self.GenerateVariableTree([(variable["Name"], variable["Type"], variable["Tree"]) for variable in self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)])
@@ -397,14 +415,36 @@
             return (call["interface"].get(name.upper(), None) is not None or 
                     call["extensible"] and EXTENSIBLE_PARAMETER.match(name.upper()) is not None)
         return False
-        
+    
+    def RefreshLineFolding(self, line_number):
+        if self.TextSyntax == "ST":
+            level = wx.stc.STC_FOLDLEVELBASE + self.GetLineIndentation(line_number)
+            line = self.GetLine(line_number).strip()
+            if line == "":
+                if line_number > 0:
+                    if LineStartswith(self.GetLine(line_number - 1).strip(), ST_BLOCK_END_KEYWORDS):
+                        level = self.GetFoldLevel(self.GetFoldParent(line_number - 1)) & wx.stc.STC_FOLDLEVELNUMBERMASK
+                    else:
+                        level = self.GetFoldLevel(line_number - 1) & wx.stc.STC_FOLDLEVELNUMBERMASK
+                if level != wx.stc.STC_FOLDLEVELBASE:
+                    level |=  wx.stc.STC_FOLDLEVELWHITEFLAG 
+            elif LineStartswith(line, ST_BLOCK_START_KEYWORDS):
+                level |= wx.stc.STC_FOLDLEVELHEADERFLAG
+            elif LineStartswith(line, ST_BLOCK_END_KEYWORDS):
+                if LineStartswith(self.GetLine(line_number - 1).strip(), ST_BLOCK_END_KEYWORDS):
+                    level = self.GetFoldLevel(self.GetFoldParent(line_number - 1)) & wx.stc.STC_FOLDLEVELNUMBERMASK
+                else:
+                    level = self.GetFoldLevel(line_number - 1) & wx.stc.STC_FOLDLEVELNUMBERMASK
+            self.SetFoldLevel(line_number, level)
+    
     def OnStyleNeeded(self, event):
         self.TextChanged = True
-        line = self.LineFromPosition(self.GetEndStyled())
-        if line == 0:
+        line_number = self.LineFromPosition(self.GetEndStyled())
+        if line_number == 0:
             start_pos = last_styled_pos = 0
         else:
-            start_pos = last_styled_pos = self.GetLineEndPosition(line - 1) + 1
+            start_pos = last_styled_pos = self.GetLineEndPosition(line_number - 1) + 1
+        self.RefreshLineFolding(line_number)
         end_pos = event.GetPosition()
         self.StartStyling(start_pos, 0xff)
         
@@ -449,6 +489,8 @@
                 last_styled_pos = current_pos
                 state = SPACE
                 line = ""
+                line_number += 1
+                self.RefreshLineFolding(line_number)
             elif line.endswith("(*") and state != COMMENT:
                 self.SetStyling(current_pos - last_styled_pos - 1, 31)
                 last_styled_pos = current_pos
@@ -585,6 +627,11 @@
         self.ShowHighlights(start_pos, end_pos)
         event.Skip()
     
+    def OnMarginClick(self, event):
+        if event.GetMargin() == 2:
+            self.ToggleFold(self.LineFromPosition(event.GetPosition()))
+        event.Skip()
+        
     def Cut(self):
         self.ResetBuffer()
         self.CmdKeyExecute(wx.stc.STC_CMD_CUT)
@@ -608,7 +655,8 @@
         if self.CallTipActive():
             self.CallTipCancel()
         key = event.GetKeyCode()
-
+        key_handled = False
+        
         # Code completion
         if key == wx.WXK_SPACE and event.ControlDown():
             
@@ -643,7 +691,24 @@
                 kw.sort()
                 self.AutoCompSetIgnoreCase(True)
                 self.AutoCompShow(len(words[-1]), " ".join(kw))
-        else:
+            key_handled = True
+        elif key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
+            if self.TextSyntax == "ST":
+                line = self.GetCurrentLine()
+                indent = self.GetLineIndentation(line)
+                if LineStartswith(self.GetLine(line).strip(), ST_BLOCK_START_KEYWORDS):
+                    indent += 2
+                self.AddText("\n" + " " * indent)
+                key_handled = True
+        elif key == wx.WXK_BACK:
+            if self.TextSyntax == "ST":
+                line = self.GetCurrentLine()
+                indent = self.GetLineIndentation(line)
+                if self.GetLine(line).strip() == "" and indent > 0:
+                    self.DelLineLeft()
+                    self.AddText(" " * max(0, indent - 2))
+                    key_handled = True
+        if not key_handled:
             event.Skip()
 
     def OnKillFocus(self, event):
--- a/plcopen/structures.py	Mon Oct 24 02:03:42 2011 +0200
+++ b/plcopen/structures.py	Mon Oct 24 02:06:34 2011 +0200
@@ -639,10 +639,10 @@
 
 
 # Keywords for Structured Text
-ST_KEYWORDS = ["TRUE", "FALSE", "IF", "THEN", "ELSIF", "ELSE", "END_IF", "CASE", "OF", "END_CASE", 
- "FOR", "TO", "BY", "DO", "END_FOR", "WHILE", "DO", "END_WHILE", "REPEAT", "UNTIL", 
- "END_REPEAT", "EXIT", "RETURN", "NOT", "MOD", "AND", "XOR", "OR"]
-
+ST_BLOCK_START_KEYWORDS = ["IF", "ELSIF", "ELSE", "CASE", "FOR", "WHILE", "REPEAT"]
+ST_BLOCK_END_KEYWORDS = ["END_IF", "END_CASE", "END_FOR", "END_WHILE", "END_REPEAT"]
+ST_KEYWORDS = ["TRUE", "FALSE", "THEN", "OF", "TO", "BY", "DO", "DO", "UNTIL", "EXIT", 
+ "RETURN", "NOT", "MOD", "AND", "XOR", "OR"] + ST_BLOCK_START_KEYWORDS + ST_BLOCK_END_KEYWORDS
 
 # All the keywords of IEC
 IEC_KEYWORDS = ["E", "TRUE", "FALSE"]