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