1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Lorenz Haas
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 // Tested with version 3.3, 3.4 and 3.4.1
27
28 #include "clangformat.h"
29
30 #include "clangformatconstants.h"
31 #include "clangformatoptionspage.h"
32
33 #include "../beautifierconstants.h"
34 #include "../beautifierplugin.h"
35
36 #include <coreplugin/actionmanager/actioncontainer.h>
37 #include <coreplugin/actionmanager/actionmanager.h>
38 #include <coreplugin/actionmanager/command.h>
39 #include <coreplugin/coreconstants.h>
40 #include <coreplugin/editormanager/editormanager.h>
41 #include <coreplugin/editormanager/ieditor.h>
42 #include <coreplugin/idocument.h>
43 #include <texteditor/formattexteditor.h>
44 #include <texteditor/textdocument.h>
45 #include <texteditor/texteditor.h>
46 #include <utils/algorithm.h>
47 #include <utils/fileutils.h>
48
49 #include <QAction>
50 #include <QMenu>
51 #include <QTextBlock>
52 #include <QTextCodec>
53
54 using namespace TextEditor;
55
56 namespace Beautifier {
57 namespace Internal {
58
ClangFormat()59 ClangFormat::ClangFormat()
60 {
61 Core::ActionContainer *menu = Core::ActionManager::createMenu("ClangFormat.Menu");
62 menu->menu()->setTitle(tr("&ClangFormat"));
63
64 m_formatFile = new QAction(BeautifierPlugin::msgFormatCurrentFile(), this);
65 Core::Command *cmd
66 = Core::ActionManager::registerAction(m_formatFile, "ClangFormat.FormatFile");
67 menu->addAction(cmd);
68 connect(m_formatFile, &QAction::triggered, this, &ClangFormat::formatFile);
69
70 m_formatLines = new QAction(BeautifierPlugin::msgFormatLines(), this);
71 cmd = Core::ActionManager::registerAction(m_formatLines, "ClangFormat.FormatLines");
72 menu->addAction(cmd);
73 connect(m_formatLines, &QAction::triggered, this, &ClangFormat::formatLines);
74
75 m_formatRange = new QAction(BeautifierPlugin::msgFormatAtCursor(), this);
76 cmd = Core::ActionManager::registerAction(m_formatRange, "ClangFormat.FormatAtCursor");
77 menu->addAction(cmd);
78 connect(m_formatRange, &QAction::triggered, this, &ClangFormat::formatAtCursor);
79
80 m_disableFormattingSelectedText
81 = new QAction(BeautifierPlugin::msgDisableFormattingSelectedText(), this);
82 cmd = Core::ActionManager::registerAction(
83 m_disableFormattingSelectedText, "ClangFormat.DisableFormattingSelectedText");
84 menu->addAction(cmd);
85 connect(m_disableFormattingSelectedText, &QAction::triggered,
86 this, &ClangFormat::disableFormattingSelectedText);
87
88 Core::ActionManager::actionContainer(Constants::MENU_ID)->addMenu(menu);
89
90 connect(&m_settings, &ClangFormatSettings::supportedMimeTypesChanged,
91 [this] { updateActions(Core::EditorManager::currentEditor()); });
92 }
93
id() const94 QString ClangFormat::id() const
95 {
96 return QLatin1String(Constants::CLANGFORMAT_DISPLAY_NAME);
97 }
98
updateActions(Core::IEditor * editor)99 void ClangFormat::updateActions(Core::IEditor *editor)
100 {
101 const bool enabled = editor && m_settings.isApplicable(editor->document());
102 m_formatFile->setEnabled(enabled);
103 m_formatRange->setEnabled(enabled);
104 }
105
formatFile()106 void ClangFormat::formatFile()
107 {
108 formatCurrentFile(command());
109 }
110
formatAtPosition(const int pos,const int length)111 void ClangFormat::formatAtPosition(const int pos, const int length)
112 {
113 const TextEditorWidget *widget = TextEditorWidget::currentTextEditorWidget();
114 if (!widget)
115 return;
116
117 const QTextCodec *codec = widget->textDocument()->codec();
118 if (!codec) {
119 formatCurrentFile(command(pos, length));
120 return;
121 }
122
123 const QString &text = widget->textAt(0, pos + length);
124 const QStringView buffer(text);
125 const int encodedOffset = codec->fromUnicode(buffer.left(pos)).size();
126 const int encodedLength = codec->fromUnicode(buffer.mid(pos, length)).size();
127 formatCurrentFile(command(encodedOffset, encodedLength));
128 }
129
formatAtCursor()130 void ClangFormat::formatAtCursor()
131 {
132 const TextEditorWidget *widget = TextEditorWidget::currentTextEditorWidget();
133 if (!widget)
134 return;
135
136 const QTextCursor tc = widget->textCursor();
137
138 if (tc.hasSelection()) {
139 const int selectionStart = tc.selectionStart();
140 formatAtPosition(selectionStart, tc.selectionEnd() - selectionStart);
141 } else {
142 // Pretend that the current line was selected.
143 // Note that clang-format will extend the range to the next bigger
144 // syntactic construct if needed.
145 const QTextBlock block = tc.block();
146 formatAtPosition(block.position(), block.length());
147 }
148 }
149
formatLines()150 void ClangFormat::formatLines()
151 {
152 const TextEditorWidget *widget = TextEditorWidget::currentTextEditorWidget();
153 if (!widget)
154 return;
155
156 const QTextCursor tc = widget->textCursor();
157 // Current line by default
158 int lineStart = tc.blockNumber() + 1;
159 int lineEnd = lineStart;
160
161 // Note that clang-format will extend the range to the next bigger
162 // syntactic construct if needed.
163 if (tc.hasSelection()) {
164 const QTextBlock start = tc.document()->findBlock(tc.selectionStart());
165 const QTextBlock end = tc.document()->findBlock(tc.selectionEnd());
166 lineStart = start.blockNumber() + 1;
167 lineEnd = end.blockNumber() + 1;
168 }
169
170 auto cmd = command();
171 cmd.addOption(QString("-lines=%1:%2").arg(QString::number(lineStart)).arg(QString::number(lineEnd)));
172 formatCurrentFile(cmd);
173 }
174
disableFormattingSelectedText()175 void ClangFormat::disableFormattingSelectedText()
176 {
177 TextEditorWidget *widget = TextEditorWidget::currentTextEditorWidget();
178 if (!widget)
179 return;
180
181 const QTextCursor tc = widget->textCursor();
182 if (!tc.hasSelection())
183 return;
184
185 // Insert start marker
186 const QTextBlock selectionStartBlock = tc.document()->findBlock(tc.selectionStart());
187 QTextCursor insertCursor(tc.document());
188 insertCursor.beginEditBlock();
189 insertCursor.setPosition(selectionStartBlock.position());
190 insertCursor.insertText("// clang-format off\n");
191 const int positionToRestore = tc.position();
192
193 // Insert end marker
194 QTextBlock selectionEndBlock = tc.document()->findBlock(tc.selectionEnd());
195 insertCursor.setPosition(selectionEndBlock.position() + selectionEndBlock.length() - 1);
196 insertCursor.insertText("\n// clang-format on");
197 insertCursor.endEditBlock();
198
199 // Reset the cursor position in order to clear the selection.
200 QTextCursor restoreCursor(tc.document());
201 restoreCursor.setPosition(positionToRestore);
202 widget->setTextCursor(restoreCursor);
203
204 // The indentation of these markers might be undesired, so reformat.
205 // This is not optimal because two undo steps will be needed to remove the markers.
206 const int reformatTextLength = insertCursor.position() - selectionStartBlock.position();
207 formatAtPosition(selectionStartBlock.position(), reformatTextLength);
208 }
209
command() const210 Command ClangFormat::command() const
211 {
212 Command command;
213 command.setExecutable(m_settings.command().toString());
214 command.setProcessing(Command::PipeProcessing);
215
216 if (m_settings.usePredefinedStyle()) {
217 const QString predefinedStyle = m_settings.predefinedStyle();
218 command.addOption("-style=" + predefinedStyle);
219 if (predefinedStyle == "File") {
220 const QString fallbackStyle = m_settings.fallbackStyle();
221 if (fallbackStyle != "Default")
222 command.addOption("-fallback-style=" + fallbackStyle);
223 }
224
225 command.addOption("-assume-filename=%file");
226 } else {
227 command.addOption("-style=file");
228 const QString path =
229 QFileInfo(m_settings.styleFileName(m_settings.customStyle())).absolutePath();
230 command.addOption("-assume-filename=" + path + QDir::separator() + "%filename");
231 }
232
233 return command;
234 }
235
isApplicable(const Core::IDocument * document) const236 bool ClangFormat::isApplicable(const Core::IDocument *document) const
237 {
238 return m_settings.isApplicable(document);
239 }
240
command(int offset,int length) const241 Command ClangFormat::command(int offset, int length) const
242 {
243 Command c = command();
244 c.addOption("-offset=" + QString::number(offset));
245 c.addOption("-length=" + QString::number(length));
246 return c;
247 }
248
249 } // namespace Internal
250 } // namespace Beautifier
251