1 #include "app.h"
2
3 #include <QDir>
4 #include <QFileInfo>
5 #include <QFileOpenEvent>
6 #include <QSettings>
7 #include <QTextStream>
8
9 #include "arguments.h"
10 #include "mainwindow.h"
11 #include "printinfo.h"
12 #include "version.h"
13
14 namespace NeovimQt {
15
16 static ShellOptions GetShellOptionsFromQSettings() noexcept;
17
18 /// A log handler for Qt messages, all messages are dumped into the file
19 /// passed via the NVIM_QT_LOG variable. Some information is only available
20 /// in debug builds (e.g. qDebug is only called in debug builds).
21 ///
22 /// In UNIX Qt prints messages to the console output, but in Windows this is
23 /// the only way to get Qt's debug/warning messages.
logger(QtMsgType type,const QMessageLogContext & ctx,const QString & msg)24 void logger(QtMsgType type, const QMessageLogContext& ctx, const QString& msg)
25 {
26 QFile logFile(qgetenv("NVIM_QT_LOG"));
27 if (logFile.open(QIODevice::Append | QIODevice::Text)) {
28 QTextStream stream(&logFile);
29 stream << msg << "\n";
30 }
31 }
32
33 #ifdef Q_OS_MAC
getLoginEnvironment(const QString & path)34 bool getLoginEnvironment(const QString& path)
35 {
36 QProcess proc;
37 proc.start(path, {"-l", "-c", "env", "-i"});
38 if (!proc.waitForFinished()) {
39 qDebug() << "Failed to execute shell to get environemnt" << path;
40 return false;
41 }
42
43 QByteArray out = proc.readAllStandardOutput();
44 foreach(const QByteArray& item, out.split('\n')) {
45 int index = item.indexOf('=');
46 if (index > 0) {
47 qputenv(item.mid(0, index), item.mid(index+1));
48 qDebug() << item.mid(0, index) << item.mid(index+1);
49 }
50 }
51 return true;
52 }
53 #endif
54
App(int & argc,char ** argv)55 App::App(int &argc, char ** argv) noexcept
56 :QApplication(argc, argv)
57 {
58 setWindowIcon(QIcon(":/neovim.svg"));
59 setApplicationDisplayName("Neovim");
60
61 #ifdef Q_OS_MAC
62 QByteArray shellPath = qgetenv("SHELL");
63 if (!getLoginEnvironment(shellPath)) {
64 getLoginEnvironment("/bin/bash");
65 }
66 #endif
67
68 if (!qgetenv("NVIM_QT_LOG").isEmpty()) {
69 qInstallMessageHandler(logger);
70 }
71
72 QByteArray stylesheet_path = qgetenv("NVIM_QT_STYLESHEET");
73 if (!stylesheet_path.isEmpty()) {
74 QFile qssfile(stylesheet_path);
75 if (qssfile.open(QIODevice::ReadOnly)) {
76 setStyleSheet(qssfile.readAll());
77 } else {
78 qWarning("Unable to open stylesheet from $NVIM_QT_STYLESHEET");
79 }
80 }
81
82 processCommandlineOptions(m_parser, arguments());
83 }
84
event(QEvent * event)85 bool App::event(QEvent *event) noexcept
86 {
87 if( event->type() == QEvent::FileOpen) {
88 QFileOpenEvent * fileOpenEvent = static_cast<QFileOpenEvent *>(event);
89 if(fileOpenEvent) {
90 emit openFilesTriggered({fileOpenEvent->url()});
91 }
92 }
93 return QApplication::event(event);
94 }
95
showUi()96 void App::showUi() noexcept
97 {
98 ShellOptions opts{ GetShellOptionsFromQSettings() };
99
100 if (m_parser.isSet("no-ext-tabline")) {
101 opts.enable_ext_tabline = false;
102 }
103
104 if (m_parser.isSet("no-ext-popupmenu")) {
105 opts.enable_ext_popupmenu = false;
106 }
107
108 #ifdef NEOVIMQT_GUI_WIDGET
109 NeovimQt::Shell *win = new NeovimQt::Shell(c);
110 win->show();
111 if (m_parser.isSet("fullscreen")) {
112 win->showFullScreen();
113 } else if (m_parser.isSet("maximized")) {
114 win->showMaximized();
115 } else {
116 win->show();
117 }
118 #else
119 NeovimQt::MainWindow *win = new NeovimQt::MainWindow(m_connector.get(), opts);
120
121 QObject::connect(instance(), SIGNAL(openFilesTriggered(const QList<QUrl>)),
122 win->shell(), SLOT(openFiles(const QList<QUrl>)));
123
124 // Window geometry should be restored only when the user does not specify
125 // one of the following command line arguments. Argument "maximized" can
126 // be safely ignored, as the loaded geometry only takes effect after the
127 // user un-maximizes the window; this behavior is desirable. Function
128 // `isSet` will issue qWarning() messages if the argument doesn't exist.
129 // Do not call isSet(...) for arguments which may not exist.
130 if (!m_parser.isSet("fullscreen") &&
131 (!hasGeometryArg() || !m_parser.isSet("geometry")) &&
132 (!hasQWindowGeometryArg() || !m_parser.isSet("qwindowgeometry")))
133 {
134 win->restoreWindowGeometry();
135 }
136
137 if (m_parser.isSet("fullscreen")) {
138 win->delayedShow(NeovimQt::MainWindow::DelayedShow::FullScreen);
139 } else if (m_parser.isSet("maximized")) {
140 win->delayedShow(NeovimQt::MainWindow::DelayedShow::Maximized);
141 } else {
142 win->delayedShow();
143 }
144 #endif
145 }
146
147 /// Initialize CLI parser with all the nvim-qt options, process the
148 /// provided arguments and check for errors.
149 ///
150 /// When appropriate this function will call QCommandLineParser::showHelp()
151 /// terminating the program.
processCommandlineOptions(QCommandLineParser & parser,QStringList arguments)152 void App::processCommandlineOptions(QCommandLineParser& parser, QStringList arguments) noexcept
153 {
154 parser.addOption(QCommandLineOption("nvim",
155 QCoreApplication::translate("main", "nvim executable path"),
156 QCoreApplication::translate("main", "nvim_path"),
157 "nvim"));
158 parser.addOption(QCommandLineOption("timeout",
159 QCoreApplication::translate("main", "Error if nvim does not responde after count milliseconds"),
160 QCoreApplication::translate("main", "ms"),
161 "20000"));
162
163 // Some platforms use --qwindowgeometry, while other platforms use the --geometry.
164 // Make the correct help message is displayed.
165 if (hasGeometryArg()) {
166 parser.addOption(QCommandLineOption("geometry",
167 QCoreApplication::translate("main", "Set initial window geometry"),
168 QCoreApplication::translate("main", "width>x<height")));
169 }
170 if (hasQWindowGeometryArg()) {
171 parser.addOption(QCommandLineOption("qwindowgeometry",
172 QCoreApplication::translate("main", "Set initial window geometry"),
173 QCoreApplication::translate("main", "width>x<height")));
174 }
175
176 parser.addOption(QCommandLineOption("stylesheet",
177 QCoreApplication::translate("main", "Apply qss stylesheet from file"),
178 QCoreApplication::translate("main", "stylesheet")));
179 parser.addOption(QCommandLineOption("maximized",
180 QCoreApplication::translate("main", "Maximize the window on startup")));
181 parser.addOption(QCommandLineOption("no-ext-tabline",
182 QCoreApplication::translate("main", "Disable the external GUI tabline")));
183 parser.addOption(QCommandLineOption("no-ext-popupmenu",
184 QCoreApplication::translate("main", "Disable the external GUI popup menu")));
185 parser.addOption(QCommandLineOption("fullscreen",
186 QCoreApplication::translate("main", "Open the window in fullscreen on startup")));
187 parser.addOption(QCommandLineOption("embed",
188 QCoreApplication::translate("main", "Communicate with Neovim over stdin/out")));
189 parser.addOption(QCommandLineOption("server",
190 QCoreApplication::translate("main", "Connect to existing Neovim instance"),
191 QCoreApplication::translate("main", "addr")));
192 parser.addOption(QCommandLineOption("spawn",
193 QCoreApplication::translate("main", "Treat positional arguments as the nvim argv")));
194 parser.addOption(QCommandLineOption({ "v", "version" },
195 QCoreApplication::translate("main", "Displays version information.")));
196
197 parser.addHelpOption();
198
199 #ifdef Q_OS_UNIX
200 parser.addOption(QCommandLineOption("nofork",
201 QCoreApplication::translate("main", "Run in foreground")));
202 #endif
203
204 parser.addPositionalArgument("file",
205 QCoreApplication::translate("main", "Edit specified file(s)"), "[file...]");
206 parser.addPositionalArgument("...", "Additional arguments are forwarded to Neovim", "[-- ...]");
207
208 parser.process(arguments);
209 }
210
checkArgumentsMayTerminate(QCommandLineParser & parser)211 void App::checkArgumentsMayTerminate(QCommandLineParser& parser) noexcept
212 {
213 if (parser.isSet("help")) {
214 parser.showHelp();
215 }
216
217 if (parser.isSet("version")) {
218 showVersionInfo(parser);
219 ::exit(0);
220 }
221
222 int exclusive = parser.isSet("server") + parser.isSet("embed") + parser.isSet("spawn");
223 if (exclusive > 1) {
224 qWarning() << "Options --server, --spawn and --embed are mutually exclusive\n";
225 ::exit(-1);
226 }
227
228 if (!parser.positionalArguments().isEmpty() &&
229 (parser.isSet("embed") || parser.isSet("server"))) {
230 qWarning() << "--embed and --server do not accept positional arguments\n";
231 ::exit(-1);
232 }
233
234 if (parser.positionalArguments().isEmpty() && parser.isSet("spawn")) {
235 qWarning() << "--spawn requires at least one positional argument\n";
236 ::exit(-1);
237 }
238
239 bool valid_timeout;
240 int timeout_opt = parser.value("timeout").toInt(&valid_timeout);
241 if (!valid_timeout || timeout_opt <= 0) {
242 qWarning() << "Invalid argument for --timeout" << parser.value("timeout");
243 ::exit(-1);
244 }
245 }
246
setupRequestTimeout()247 void App::setupRequestTimeout() noexcept
248 {
249 if (!m_connector)
250 {
251 return;
252 }
253
254 m_connector->setRequestTimeout(m_parser.value("timeout").toInt());
255 }
256
getNeovimArgs()257 QStringList App::getNeovimArgs() noexcept
258 {
259 QStringList neovimArgs{ "--cmd","set termguicolors" };
260
261 QString runtimePath{ getRuntimePath() };
262 if (runtimePath.isEmpty()) {
263 return { "--cmd","set termguicolors" };
264 }
265
266 return { "--cmd", QString{ "let &rtp.=',%1'" }.arg(runtimePath),
267 "--cmd","set termguicolors" };
268 }
269
connectToRemoteNeovim()270 void App::connectToRemoteNeovim() noexcept
271 {
272 if (m_parser.isSet("embed")) {
273 m_connector = std::unique_ptr<NeovimConnector>{ NeovimQt::NeovimConnector::fromStdinOut() };
274 setupRequestTimeout();
275 return;
276 }
277
278 if (m_parser.isSet("server")) {
279 QString server = m_parser.value("server");
280 m_connector = std::unique_ptr<NeovimConnector>{ NeovimQt::NeovimConnector::connectToNeovim(server) };
281 setupRequestTimeout();
282 return;
283 }
284
285 if (m_parser.isSet("spawn") && !m_parser.positionalArguments().isEmpty()) {
286 const QStringList& args = m_parser.positionalArguments();
287 m_connector = std::unique_ptr<NeovimConnector> { NeovimQt::NeovimConnector::spawn(args.mid(1), args.at(0)) };
288 setupRequestTimeout();
289 return;
290 }
291
292 QStringList neovimArgs{ getNeovimArgs() };
293
294 // Append positional file arguments to nvim.
295 neovimArgs.append(m_parser.positionalArguments());
296
297 m_connector = std::unique_ptr<NeovimConnector>{ NeovimQt::NeovimConnector::spawn(neovimArgs, m_parser.value("nvim")) };
298 setupRequestTimeout();
299 return;
300 }
301
GetShellOptionsFromQSettings()302 static ShellOptions GetShellOptionsFromQSettings() noexcept
303 {
304 ShellOptions opts{};
305 QSettings settings("nvim-qt", "nvim-qt");
306
307 QVariant ext_linegrid{ settings.value("ext_linegrid", opts.enable_ext_linegrid) };
308 QVariant ext_popupmenu{ settings.value("ext_popupmenu", opts.enable_ext_popupmenu) };
309 QVariant ext_tabline{ settings.value("ext_tabline", opts.enable_ext_popupmenu) };
310
311 if (ext_linegrid.canConvert<bool>())
312 {
313 opts.enable_ext_linegrid = ext_linegrid.toBool();
314 }
315
316 if (ext_popupmenu.canConvert<bool>())
317 {
318 opts.enable_ext_popupmenu = ext_popupmenu.toBool();
319 }
320
321 if (ext_tabline.canConvert<bool>())
322 {
323 opts.enable_ext_tabline= ext_tabline.toBool();
324 }
325
326 return opts;
327 }
328
GetNeovimVersionInfo(const QString & nvim)329 static QString GetNeovimVersionInfo(const QString& nvim) noexcept
330 {
331 QProcess nvimproc;
332 nvimproc.start(nvim, { "--version" });
333 if (!nvimproc.waitForFinished(2000 /*msec*/)) {
334 return QCoreApplication::translate("main", "Neovim Not Found!");
335 }
336
337 return nvimproc.readAllStandardOutput();
338 }
339
showVersionInfo(QCommandLineParser & parser)340 void App::showVersionInfo(QCommandLineParser& parser) noexcept
341 {
342 QString versionInfo;
343 QTextStream out{ &versionInfo };
344
345 const QString nvimExecutable { (parser.isSet("nvim")) ?
346 parser.value("nvim") : "nvim" };
347
348 out << "NVIM-QT v" << PROJECT_VERSION << endl;
349 out << "Build type: " << CMAKE_BUILD_TYPE << endl;
350 out << "Compilation:" << CMAKE_CXX_FLAGS << endl;
351 out << "Qt Version: " << QT_VERSION_STR << endl;
352 out << "Environment: " << endl;
353 out << " nvim: " << nvimExecutable << endl;
354 out << " args: " << getNeovimArgs().join(" ") << endl;
355 out << " runtime: " << getRuntimePath() << endl;
356
357 out << endl;
358
359 out << GetNeovimVersionInfo(nvimExecutable) << endl;
360
361 PrintInfo(versionInfo);
362 }
363
364 } // namespace NeovimQt
365