|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a typing completer for Ruby. |
|
8 """ |
|
9 |
|
10 import re |
|
11 |
|
12 from PyQt6.Qsci import QsciLexerRuby, QsciScintilla |
|
13 |
|
14 from .CompleterBase import CompleterBase |
|
15 |
|
16 import Preferences |
|
17 |
|
18 |
|
19 class CompleterRuby(CompleterBase): |
|
20 """ |
|
21 Class implementing typing completer for Ruby. |
|
22 """ |
|
23 def __init__(self, editor, parent=None): |
|
24 """ |
|
25 Constructor |
|
26 |
|
27 @param editor reference to the editor object (QScintilla.Editor) |
|
28 @param parent reference to the parent object (QObject) |
|
29 """ |
|
30 super().__init__(editor, parent) |
|
31 |
|
32 self.__beginRX = re.compile(r"""^=begin """) |
|
33 self.__beginNlRX = re.compile(r"""^=begin\r?\n""") |
|
34 self.__hereRX = re.compile(r"""<<-?['"]?(\w*)['"]?\r?\n""") |
|
35 |
|
36 self.readSettings() |
|
37 |
|
38 def readSettings(self): |
|
39 """ |
|
40 Public slot called to reread the configuration parameters. |
|
41 """ |
|
42 self.setEnabled(Preferences.getEditorTyping("Ruby/EnabledTypingAids")) |
|
43 self.__insertClosingBrace = Preferences.getEditorTyping( |
|
44 "Ruby/InsertClosingBrace") |
|
45 self.__indentBrace = Preferences.getEditorTyping( |
|
46 "Ruby/IndentBrace") |
|
47 self.__skipBrace = Preferences.getEditorTyping( |
|
48 "Ruby/SkipBrace") |
|
49 self.__insertQuote = Preferences.getEditorTyping( |
|
50 "Ruby/InsertQuote") |
|
51 self.__insertBlank = Preferences.getEditorTyping( |
|
52 "Ruby/InsertBlank") |
|
53 self.__insertHereDoc = Preferences.getEditorTyping( |
|
54 "Ruby/InsertHereDoc") |
|
55 self.__insertInlineDoc = Preferences.getEditorTyping( |
|
56 "Ruby/InsertInlineDoc") |
|
57 |
|
58 def charAdded(self, charNumber): |
|
59 """ |
|
60 Public slot called to handle the user entering a character. |
|
61 |
|
62 @param charNumber value of the character entered (integer) |
|
63 """ |
|
64 char = chr(charNumber) |
|
65 if char not in ['(', ')', '{', '}', '[', ']', ',', "'", '"', |
|
66 '\n', ' ']: |
|
67 return # take the short route |
|
68 |
|
69 line, col = self.editor.getCursorPosition() |
|
70 |
|
71 if ( |
|
72 self.__inComment(line, col) or |
|
73 self.__inDoubleQuotedString() or |
|
74 self.__inSingleQuotedString() or |
|
75 self.__inHereDocument() or |
|
76 self.__inInlineDocument() |
|
77 ): |
|
78 return |
|
79 |
|
80 # open parenthesis |
|
81 # insert closing parenthesis and self |
|
82 if char == '(': |
|
83 txt = self.editor.text(line)[:col] |
|
84 if self.__insertClosingBrace: |
|
85 self.editor.insert(')') |
|
86 |
|
87 # closing parenthesis |
|
88 # skip matching closing parenthesis |
|
89 elif char in [')', '}', ']']: |
|
90 txt = self.editor.text(line) |
|
91 if col < len(txt) and char == txt[col] and self.__skipBrace: |
|
92 self.editor.setSelection(line, col, line, col + 1) |
|
93 self.editor.removeSelectedText() |
|
94 |
|
95 # space |
|
96 # complete inline documentation |
|
97 elif char == ' ': |
|
98 txt = self.editor.text(line)[:col] |
|
99 if self.__insertInlineDoc and self.__beginRX.fullmatch(txt): |
|
100 self.editor.insert('=end') |
|
101 |
|
102 # comma |
|
103 # insert blank |
|
104 elif char == ',' and self.__insertBlank: |
|
105 self.editor.insert(' ') |
|
106 self.editor.setCursorPosition(line, col + 1) |
|
107 |
|
108 # open curly brace |
|
109 # insert closing brace |
|
110 elif char == '{' and self.__insertClosingBrace: |
|
111 self.editor.insert('}') |
|
112 |
|
113 # open bracket |
|
114 # insert closing bracket |
|
115 elif char == '[' and self.__insertClosingBrace: |
|
116 self.editor.insert(']') |
|
117 |
|
118 # double quote |
|
119 # insert double quote |
|
120 elif char == '"' and self.__insertQuote: |
|
121 self.editor.insert('"') |
|
122 |
|
123 # quote |
|
124 # insert quote |
|
125 elif char == '\'' and self.__insertQuote: |
|
126 self.editor.insert('\'') |
|
127 |
|
128 # new line |
|
129 # indent to opening brace, complete inline documentation |
|
130 elif char == '\n': |
|
131 txt = self.editor.text(line - 1) |
|
132 if self.__insertInlineDoc and self.__beginNlRX.fullmatch(txt): |
|
133 self.editor.insert('=end') |
|
134 elif self.__insertHereDoc and self.__hereRX.fullmatch(txt): |
|
135 self.editor.insert(self.__hereRX.fullmatch(txt).group(1)) |
|
136 elif self.__indentBrace and re.search(":\r?\n", txt) is None: |
|
137 stxt = txt.strip() |
|
138 if stxt and stxt[-1] in ("(", "[", "{"): |
|
139 # indent one more level |
|
140 self.editor.indent(line) |
|
141 self.editor.editorCommand(QsciScintilla.SCI_VCHOME) |
|
142 else: |
|
143 # indent to the level of the opening brace |
|
144 openCount = len(re.findall("[({[]", txt)) |
|
145 closeCount = len(re.findall(r"[)}\]]", txt)) |
|
146 if openCount > closeCount: |
|
147 openCount = 0 |
|
148 closeCount = 0 |
|
149 openList = list(re.finditer("[({[]", txt)) |
|
150 index = len(openList) - 1 |
|
151 while index > -1 and openCount == closeCount: |
|
152 lastOpenIndex = openList[index].start() |
|
153 txt2 = txt[lastOpenIndex:] |
|
154 openCount = len(re.findall("[({[]", txt2)) |
|
155 closeCount = len(re.findall(r"[)}\]]", txt2)) |
|
156 index -= 1 |
|
157 if openCount > closeCount and lastOpenIndex > col: |
|
158 self.editor.insert(' ' * (lastOpenIndex - col + 1)) |
|
159 self.editor.setCursorPosition(line, |
|
160 lastOpenIndex + 1) |
|
161 |
|
162 def __inComment(self, line, col): |
|
163 """ |
|
164 Private method to check, if the cursor is inside a comment. |
|
165 |
|
166 @param line current line (integer) |
|
167 @param col current position within line (integer) |
|
168 @return flag indicating, if the cursor is inside a comment (boolean) |
|
169 """ |
|
170 txt = self.editor.text(line) |
|
171 if col == len(txt): |
|
172 col -= 1 |
|
173 while col >= 0: |
|
174 if txt[col] == "#": |
|
175 return True |
|
176 col -= 1 |
|
177 return False |
|
178 |
|
179 def __inDoubleQuotedString(self): |
|
180 """ |
|
181 Private method to check, if the cursor is within a double quoted |
|
182 string. |
|
183 |
|
184 @return flag indicating, if the cursor is inside a double |
|
185 quoted string (boolean) |
|
186 """ |
|
187 return self.editor.currentStyle() == QsciLexerRuby.DoubleQuotedString |
|
188 |
|
189 def __inSingleQuotedString(self): |
|
190 """ |
|
191 Private method to check, if the cursor is within a single quoted |
|
192 string. |
|
193 |
|
194 @return flag indicating, if the cursor is inside a single |
|
195 quoted string (boolean) |
|
196 """ |
|
197 return self.editor.currentStyle() == QsciLexerRuby.SingleQuotedString |
|
198 |
|
199 def __inHereDocument(self): |
|
200 """ |
|
201 Private method to check, if the cursor is within a here document. |
|
202 |
|
203 @return flag indicating, if the cursor is inside a here document |
|
204 (boolean) |
|
205 """ |
|
206 return self.editor.currentStyle() == QsciLexerRuby.HereDocument |
|
207 |
|
208 def __inInlineDocument(self): |
|
209 """ |
|
210 Private method to check, if the cursor is within an inline document. |
|
211 |
|
212 @return flag indicating, if the cursor is inside an inline document |
|
213 (boolean) |
|
214 """ |
|
215 return self.editor.currentStyle() == QsciLexerRuby.POD |