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
28CppRefacter::CppRefacter(QObject *parent) : QObject(parent)
29{
30
31}
32
33bool 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
63bool 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
101static QString fullParentName(PStatement statement) {
102 PStatement parent = statement->parentScope.lock();
103 if (parent) {
104 return parent->fullName;
105 } else {
106 return "";
107 }
108}
109void 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
157void 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
173void 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
193PSearchResultTreeItem 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
263void 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