1 | /* |
2 | * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) |
3 | * |
4 | * This program is free software: you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License as published by |
6 | * the Free Software Foundation, either version 3 of the License, or |
7 | * (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program. If not, see <https://www.gnu.org/licenses/>. |
16 | */ |
17 | #include "cpprefacter.h" |
18 | #include "mainwindow.h" |
19 | #include "settings.h" |
20 | #include "editor.h" |
21 | #include "editorlist.h" |
22 | #include <QFile> |
23 | #include <QMessageBox> |
24 | #include <QTextCodec> |
25 | #include "HighlighterManager.h" |
26 | #include "project.h" |
27 | |
28 | CppRefacter::CppRefacter(QObject *parent) : QObject(parent) |
29 | { |
30 | |
31 | } |
32 | |
33 | bool CppRefacter::findOccurence(Editor *editor, const QSynedit::BufferCoord &pos) |
34 | { |
35 | if (!editor->parser()) |
36 | return false; |
37 | if (!editor->parser()->freeze()) |
38 | return false; |
39 | auto action = finally([&editor]{ |
40 | editor->parser()->unFreeze(); |
41 | }); |
42 | // get full phrase (such as s.name instead of name) |
43 | QStringList expression = editor->getExpressionAtPosition(pos); |
44 | // Find it's definition |
45 | PStatement statement = editor->parser()->findStatementOf( |
46 | editor->filename(), |
47 | expression, |
48 | pos.line); |
49 | // definition of the symbol not found |
50 | if (!statement) |
51 | return false; |
52 | |
53 | std::shared_ptr<Project> project = pMainWindow->project(); |
54 | if (editor->inProject() && project) { |
55 | doFindOccurenceInProject(statement,project,editor->parser()); |
56 | } else { |
57 | doFindOccurenceInEditor(statement,editor,editor->parser()); |
58 | } |
59 | pMainWindow->searchResultModel()->notifySearchResultsUpdated(); |
60 | return true; |
61 | } |
62 | |
63 | bool CppRefacter::findOccurence(const QString &statementFullname, SearchFileScope scope) |
64 | { |
65 | PCppParser parser; |
66 | Editor * editor=nullptr; |
67 | std::shared_ptr<Project> project; |
68 | if (scope == SearchFileScope::currentFile) { |
69 | editor = pMainWindow->editorList()->getEditor(); |
70 | if (!editor) |
71 | return false; |
72 | parser = editor->parser(); |
73 | } else if (scope == SearchFileScope::wholeProject) { |
74 | project = pMainWindow->project(); |
75 | if (!project) |
76 | return false; |
77 | parser = project->cppParser(); |
78 | } |
79 | if (!parser) |
80 | return false; |
81 | { |
82 | parser->freeze(); |
83 | auto action = finally([&parser]{ |
84 | parser->unFreeze(); |
85 | }); |
86 | PStatement statement = parser->findStatement(statementFullname); |
87 | // definition of the symbol not found |
88 | if (!statement) |
89 | return false; |
90 | |
91 | if (scope == SearchFileScope::wholeProject) { |
92 | doFindOccurenceInProject(statement,project,parser); |
93 | } else if (scope == SearchFileScope::currentFile) { |
94 | doFindOccurenceInEditor(statement, editor,parser); |
95 | } |
96 | pMainWindow->searchResultModel()->notifySearchResultsUpdated(); |
97 | return true; |
98 | } |
99 | } |
100 | |
101 | static QString fullParentName(PStatement statement) { |
102 | PStatement parent = statement->parentScope.lock(); |
103 | if (parent) { |
104 | return parent->fullName; |
105 | } else { |
106 | return "" ; |
107 | } |
108 | } |
109 | void CppRefacter::renameSymbol(Editor *editor, const QSynedit::BufferCoord &pos, const QString &newWord) |
110 | { |
111 | if (!editor->parser()->freeze()) |
112 | return; |
113 | auto action = finally([&editor]{ |
114 | editor->parser()->unFreeze(); |
115 | }); |
116 | // get full phrase (such as s.name instead of name) |
117 | QStringList expression; |
118 | QChar s=editor->charAt(pos); |
119 | if (!editor->isIdentChar(s)) { |
120 | expression = editor->getExpressionAtPosition(QSynedit::BufferCoord{pos.ch-1,pos.line}); |
121 | } else { |
122 | expression = editor->getExpressionAtPosition(pos); |
123 | } |
124 | // Find it's definition |
125 | PStatement oldStatement = editor->parser()->findStatementOf( |
126 | editor->filename(), |
127 | expression, |
128 | pos.line); |
129 | // definition of the symbol not found |
130 | if (!oldStatement) |
131 | return; |
132 | QString oldScope = fullParentName(oldStatement); |
133 | // found but not in this file |
134 | if (editor->filename() != oldStatement->fileName |
135 | || editor->filename() != oldStatement->definitionFileName) { |
136 | QMessageBox::critical(editor, |
137 | tr("Rename Symbol Error" ), |
138 | tr("Can't rename symbols not defined in this file." )); |
139 | return; |
140 | } |
141 | |
142 | QStringList newExpression = expression; |
143 | newExpression[newExpression.count()-1]=newWord; |
144 | PStatement newStatement = editor->parser()->findStatementOf( |
145 | editor->filename(), |
146 | newExpression, |
147 | pos.line); |
148 | if (newStatement && fullParentName(newStatement) == oldScope) { |
149 | QMessageBox::critical(editor, |
150 | tr("Rename Symbol Error" ), |
151 | tr("New symbol already exists!" )); |
152 | return; |
153 | } |
154 | renameSymbolInFile(editor->filename(),oldStatement,newWord, editor->parser()); |
155 | } |
156 | |
157 | void CppRefacter::doFindOccurenceInEditor(PStatement statement , Editor *editor, const PCppParser &parser) |
158 | { |
159 | PSearchResults results = pMainWindow->searchResultModel()->addSearchResults( |
160 | statement->command, |
161 | statement->fullName, |
162 | SearchFileScope::currentFile |
163 | ); |
164 | PSearchResultTreeItem item = findOccurenceInFile( |
165 | editor->filename(), |
166 | statement, |
167 | parser); |
168 | if (item && !(item->results.isEmpty())) { |
169 | results->results.append(item); |
170 | } |
171 | } |
172 | |
173 | void CppRefacter::doFindOccurenceInProject(PStatement statement, std::shared_ptr<Project> project, const PCppParser &parser) |
174 | { |
175 | PSearchResults results = pMainWindow->searchResultModel()->addSearchResults( |
176 | statement->command, |
177 | statement->fullName, |
178 | SearchFileScope::wholeProject |
179 | ); |
180 | foreach (const PProjectUnit& unit, project->units()) { |
181 | if (isCFile(unit->fileName()) || isHFile(unit->fileName())) { |
182 | PSearchResultTreeItem item = findOccurenceInFile( |
183 | unit->fileName(), |
184 | statement, |
185 | parser); |
186 | if (item && !(item->results.isEmpty())) { |
187 | results->results.append(item); |
188 | } |
189 | } |
190 | } |
191 | } |
192 | |
193 | PSearchResultTreeItem CppRefacter::findOccurenceInFile( |
194 | const QString &filename, |
195 | const PStatement &statement, |
196 | const PCppParser& parser) |
197 | { |
198 | PSearchResultTreeItem parentItem = std::make_shared<SearchResultTreeItem>(); |
199 | parentItem->filename = filename; |
200 | parentItem->parent = nullptr; |
201 | QStringList buffer; |
202 | Editor editor(nullptr); |
203 | if (pMainWindow->editorList()->getContentFromOpenedEditor( |
204 | filename,buffer)){ |
205 | editor.document()->setContents(buffer); |
206 | } else { |
207 | QByteArray encoding; |
208 | editor.document()->loadFromFile(filename,ENCODING_AUTO_DETECT,encoding); |
209 | } |
210 | editor.setHighlighter(HighlighterManager().getCppHighlighter()); |
211 | int posY = 0; |
212 | while (posY < editor.document()->count()) { |
213 | QString line = editor.document()->getString(posY); |
214 | if (line.isEmpty()) { |
215 | posY++; |
216 | continue; |
217 | } |
218 | |
219 | if (posY == 0) { |
220 | editor.highlighter()->resetState(); |
221 | } else { |
222 | editor.highlighter()->setState( |
223 | editor.document()->ranges(posY-1)); |
224 | } |
225 | editor.highlighter()->setLine(line,posY); |
226 | while (!editor.highlighter()->eol()) { |
227 | int start = editor.highlighter()->getTokenPos() + 1; |
228 | QString token = editor.highlighter()->getToken(); |
229 | QSynedit::PHighlighterAttribute attr = editor.highlighter()->getTokenAttribute(); |
230 | if (!attr || attr!=editor.highlighter()->commentAttribute()) { |
231 | if (token == statement->command) { |
232 | //same name symbol , test if the same statement; |
233 | QSynedit::BufferCoord p; |
234 | p.line = posY+1; |
235 | p.ch = start+1; |
236 | |
237 | QStringList expression = editor.getExpressionAtPosition(p); |
238 | PStatement tokenStatement = parser->findStatementOf( |
239 | filename, |
240 | expression, p.line); |
241 | if (tokenStatement |
242 | && (tokenStatement->line == statement->line) |
243 | && (tokenStatement->fileName == statement->fileName)) { |
244 | PSearchResultTreeItem item = std::make_shared<SearchResultTreeItem>(); |
245 | item->filename = filename; |
246 | item->line = p.line; |
247 | item->start = start; |
248 | item->len = token.length(); |
249 | item->parent = parentItem.get(); |
250 | item->text = line; |
251 | item->text.replace('\t',' '); |
252 | parentItem->results.append(item); |
253 | } |
254 | } |
255 | } |
256 | editor.highlighter()->next(); |
257 | } |
258 | posY++; |
259 | } |
260 | return parentItem; |
261 | } |
262 | |
263 | void CppRefacter::renameSymbolInFile(const QString &filename, const PStatement &statement, const QString &newWord, const PCppParser &parser) |
264 | { |
265 | QStringList buffer; |
266 | Editor editor(nullptr); |
267 | if (pMainWindow->editorList()->getContentFromOpenedEditor( |
268 | filename,buffer)){ |
269 | editor.document()->setContents(buffer); |
270 | } else { |
271 | QByteArray encoding; |
272 | editor.document()->loadFromFile(filename,ENCODING_AUTO_DETECT,encoding); |
273 | } |
274 | QStringList newContents; |
275 | editor.setHighlighter(HighlighterManager().getCppHighlighter()); |
276 | int posY = 0; |
277 | while (posY < editor.document()->count()) { |
278 | QString line = editor.document()->getString(posY); |
279 | |
280 | if (posY == 0) { |
281 | editor.highlighter()->resetState(); |
282 | } else { |
283 | editor.highlighter()->setState( |
284 | editor.document()->ranges(posY-1)); |
285 | } |
286 | editor.highlighter()->setLine(line,posY); |
287 | QString newLine; |
288 | while (!editor.highlighter()->eol()) { |
289 | int start = editor.highlighter()->getTokenPos() + 1; |
290 | QString token = editor.highlighter()->getToken(); |
291 | if (token == statement->command) { |
292 | //same name symbol , test if the same statement; |
293 | QSynedit::BufferCoord p; |
294 | p.line = posY+1; |
295 | p.ch = start; |
296 | |
297 | QStringList expression = editor.getExpressionAtPosition(p); |
298 | PStatement tokenStatement = parser->findStatementOf( |
299 | filename, |
300 | expression, p.line); |
301 | if (tokenStatement |
302 | && (tokenStatement->line == statement->line) |
303 | && (tokenStatement->fileName == statement->fileName)) { |
304 | token = newWord; |
305 | } |
306 | } |
307 | newLine += token; |
308 | editor.highlighter()->next(); |
309 | } |
310 | newContents.append(newLine); |
311 | posY++; |
312 | } |
313 | |
314 | Editor * oldEditor = pMainWindow->editorList()->getOpenedEditorByFilename(filename); |
315 | if (oldEditor) { |
316 | QSynedit::BufferCoord oldXY=oldEditor->caretXY(); |
317 | int topLine = oldEditor->topLine(); |
318 | int leftChar = oldEditor->leftChar(); |
319 | oldEditor->beginUndoBlock(); |
320 | oldEditor->addLeftTopToUndo(); |
321 | oldEditor->addCaretToUndo(); |
322 | oldEditor->replaceAll(newContents.join(oldEditor->lineBreak())); |
323 | oldEditor->setTopLine(topLine); |
324 | oldEditor->setLeftChar(leftChar); |
325 | oldEditor->setCaretXY(oldXY); |
326 | oldEditor->endUndoBlock(); |
327 | } else { |
328 | QByteArray realEncoding; |
329 | QFile file(filename); |
330 | editor.document()->saveToFile(file,ENCODING_AUTO_DETECT, |
331 | pSettings->editor().defaultEncoding(), |
332 | realEncoding); |
333 | } |
334 | } |
335 | |