1 /**************************************************************************
2 ** This file is part of LiteIDE
3 **
4 ** Copyright (c) 2011-2019 LiteIDE. All rights reserved.
5 **
6 ** This library is free software; you can redistribute it and/or
7 ** modify it under the terms of the GNU Lesser General Public
8 ** License as published by the Free Software Foundation; either
9 ** version 2.1 of the License, or (at your option) any later version.
10 **
11 ** This library is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 ** Lesser General Public License for more details.
15 **
16 ** In addition, as a special exception, that plugins developed for LiteIDE,
17 ** are allowed to remain closed sourced and can be distributed under any license .
18 ** These rights are included in the file LGPL_EXCEPTION.txt in this package.
19 **
20 **************************************************************************/
21 // Module: golangcode.cpp
22 // Creator: visualfc <visualfc@gmail.com>
23
24 #include "golangcode.h"
25 #include "golangcode_global.h"
26 #include "fileutil/fileutil.h"
27 #include "processex/processex.h"
28 #include "../liteeditor/faketooltip.h"
29 #include <QProcess>
30 #include <QTextDocument>
31 #include <QAbstractItemView>
32 #include <QApplication>
33 #include <QDesktopWidget>
34 #include <QLabel>
35 #include <QHBoxLayout>
36 #include <QPlainTextEdit>
37 #include <QTimer>
38 #include <QScrollBar>
39 #include <QDebug>
40 //lite_memory_check_begin
41 #if defined(WIN32) && defined(_MSC_VER) && defined(_DEBUG)
42 #define _CRTDBG_MAP_ALLOC
43 #include <stdlib.h>
44 #include <crtdbg.h>
45 #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
46 #define new DEBUG_NEW
47 #endif
48 //lite_memory_check_end
49
50 int GolangCode::g_gocodeInstCount = 0;
51
GolangCode(LiteApi::IApplication * app,QObject * parent)52 GolangCode::GolangCode(LiteApi::IApplication *app, QObject *parent) :
53 QObject(parent),
54 m_liteApp(app),
55 m_editor(0),
56 m_completer(0),
57 m_closeOnExit(true),
58 m_autoUpdatePkg(false),
59 m_allImportHint(true)
60 {
61 g_gocodeInstCount++;
62 m_gocodeProcess = new Process(this);
63 m_gocodeSetProcess = new Process(this);
64 m_gocodeImportProcess = new Process(this);
65 m_importProcess = new Process(this);
66 m_gocodeProcess->setWorkingDirectory(m_liteApp->applicationPath());
67 m_gocodeSetProcess->setWorkingDirectory(m_liteApp->applicationPath());
68 m_gocodeImportProcess->setWorkingDirectory(m_liteApp->applicationPath());
69 connect(m_gocodeProcess,SIGNAL(started()),this,SLOT(started()));
70 connect(m_gocodeProcess,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(finished(int,QProcess::ExitStatus)));
71 connect(m_gocodeImportProcess,SIGNAL(started()),this,SLOT(gocodeImportStarted()));
72 connect(m_gocodeImportProcess,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(gocodeImportFinished(int,QProcess::ExitStatus)));
73 connect(m_importProcess,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(importFinished(int,QProcess::ExitStatus)));
74 m_envManager = LiteApi::getEnvManager(m_liteApp);
75 if (m_envManager) {
76 connect(m_envManager,SIGNAL(currentEnvChanged(LiteApi::IEnv*)),this,SLOT(currentEnvChanged(LiteApi::IEnv*)));
77 }
78 m_envManager = LiteApi::findExtensionObject<LiteApi::IEnvManager*>(m_liteApp,"LiteApi.IEnvManager");
79 m_golangAst = LiteApi::findExtensionObject<LiteApi::IGolangAst*>(m_liteApp,"LiteApi.IGolangAst");
80 m_pkgImportTip = new ImportPkgTip(m_liteApp,this);
81 connect(m_pkgImportTip,SIGNAL(import(QString,int)),this,SLOT(import(QString,int)));
82 connect(m_liteApp->editorManager(),SIGNAL(currentEditorChanged(LiteApi::IEditor*)),this,SLOT(currentEditorChanged(LiteApi::IEditor*)));
83 connect(m_liteApp->optionManager(),SIGNAL(applyOption(QString)),this,SLOT(applyOption(QString)));
84 connect(m_liteApp,SIGNAL(loaded()),this,SLOT(appLoaded()));
85 applyOption("option/golangcode");
86 }
87
applyOption(QString id)88 void GolangCode::applyOption(QString id)
89 {
90 if (id != "option/golangcode") return;
91 m_closeOnExit = m_liteApp->settings()->value(GOLANGCODE_EXITCLOSE,true).toBool();
92 m_autoUpdatePkg = m_liteApp->settings()->value(GOLANGCODE_AUTOBUILD,false).toBool();
93 m_allImportHint = m_liteApp->settings()->value(GOLANGCODE_IMPORTHINT_GOPATH,true).toBool();
94 QStringList args;
95 args << "set" << "autobuild";
96 if (m_autoUpdatePkg) {
97 args << "true";
98 } else {
99 args << "false";
100 }
101 if (!m_gocodeSetProcess->isStop()) {
102 m_gocodeSetProcess->stopAndWait(100,2000);
103 }
104 m_gocodeSetProcess->startEx(m_gocodeCmd,args);
105 }
106
appLoaded()107 void GolangCode::appLoaded()
108 {
109 loadPkgList();
110 LiteApi::IGoEnvManger *goEnv = LiteApi::getGoEnvManager(m_liteApp);
111 if (goEnv) {
112 connect(goEnv,SIGNAL(customGOPATHChanged(QString)),this,SLOT(customGOPATHChanged(QString)));
113 connect(goEnv,SIGNAL(globalGOPATHChanged()),this,SLOT(globalGOPATHChanged()));
114 }
115 }
116
import(const QString & import,int startPos)117 void GolangCode::import(const QString &import, int startPos)
118 {
119 QPlainTextEdit *ed = LiteApi::getPlainTextEdit(m_editor);
120 if (!ed) {
121 return;
122 }
123 QTextBlock block = ed->document()->firstBlock();
124 int pos1 = -1;
125 int pos2 = -1;
126 int pos3 = -1;
127 int pos4 = -1;
128 int offset = 0;
129 while (block.isValid()) {
130 QString text = block.text();
131 if (text.startsWith("/*")) {
132 block = block.next();
133 while(block.isValid()) {
134 if (block.text().endsWith("*/")) {
135 break;
136 }
137 block = block.next();
138 }
139 if (!block.isValid()) {
140 break;
141 }
142 } else if (text.startsWith("var")) {
143 break;
144 } else if (text.startsWith("func")) {
145 break;
146 } else if (text.startsWith("package ")) {
147 pos1 = block.position()+block.length();
148 } else if (pos1 != -1 && text.startsWith("import (")) {
149 pos2 = block.position()+block.length();
150 break;
151 } else if (pos1 != -1 && text.startsWith("import ")) {
152 QString path = text.right(text.length()-7).trimmed();
153 if (!path.startsWith("\"C\"")) {
154 pos3 = block.position()+ 7;
155 pos4 = block.position()+block.length();
156 break;
157 }
158 }
159 block = block.next();
160 }
161 if (pos1 < 0) {
162 return;
163 }
164 QString text = "\t\""+import+"\"\n";
165 QTextCursor cur = ed->textCursor();
166 int orgPos = cur.position();
167 cur.beginEditBlock();
168 if (pos2 < 0) {
169 if (pos3 < 0) {
170 pos2 = pos1;
171 text = "\nimport (\n\t\""+import+"\"\n)\n";
172 } else {
173 cur.setPosition(pos3);
174 cur.insertText("(\n\t");
175 pos2 = pos4+3;
176 offset += 3;
177 text = "\t\""+import+"\"\n)\n";
178 }
179 }
180 cur.setPosition(pos2);
181 cur.insertText(text);
182 cur.setPosition(orgPos+text.length()+offset);
183 cur.endEditBlock();
184 ed->setTextCursor(cur);
185 if (orgPos == startPos) {
186 prefixChanged(cur,m_lastPrefix,true);
187 }
188 }
189
check_import(const QString & path,const QString & id)190 bool check_import(const QString &path, const QString &id)
191 {
192 int start = path.indexOf("\"");
193 if (start >= 0) {
194 int end = path.indexOf("\"",start+1);
195 if (end > 0) {
196 QString name = path.left(start).trimmed();
197 if (!name.isEmpty()) {
198 if (name == id) {
199 return true;
200 }
201 } else {
202 QString tmp = path.mid(start+1,end-start-1);
203 if (tmp == id) {
204 return true;
205 }
206 if (tmp.endsWith("/"+id)) {
207 return true;
208 }
209 }
210 }
211 }
212 return false;
213 }
214
findImport(const QString & id)215 bool GolangCode::findImport(const QString &id)
216 {
217 QPlainTextEdit *ed = LiteApi::getPlainTextEdit(m_editor);
218 if (!ed) {
219 return false;
220 }
221 QTextBlock block = ed->document()->firstBlock();
222 int pos1 = -1;
223 while (block.isValid()) {
224 QString text = block.text().trimmed();
225 if (text.startsWith("/*")) {
226 block = block.next();
227 while(block.isValid()) {
228 if (block.text().endsWith("*/")) {
229 break;
230 }
231 block = block.next();
232 }
233 if (!block.isValid()) {
234 break;
235 }
236 } else if (text.startsWith("var")) {
237 break;
238 } else if (text.startsWith("func")) {
239 break;
240 } else if (text.startsWith("package ")) {
241 pos1 = block.position()+block.length();
242 } else if (pos1 != -1 && text.startsWith("import (")) {
243 block = block.next();
244 while(block.isValid()) {
245 QString text = block.text().trimmed();
246 if (text.startsWith(")")) {
247 break;
248 }
249 //skip
250 if (text.startsWith("/*")) {
251 block = block.next();
252 while(block.isValid()) {
253 if (block.text().endsWith("*/")) {
254 break;
255 }
256 block = block.next();
257 }
258 if (!block.isValid()) {
259 break;
260 }
261 }
262 if (text.startsWith("//")) {
263 block = block.next();
264 continue;
265 }
266 if (check_import(text,id)) {
267 return true;
268 }
269 block = block.next();
270 }
271 } else if (pos1 != -1 && text.startsWith("import ")) {
272 QString path = text.right(text.length()-7);
273 if (check_import(path,id)) {
274 return true;
275 }
276 }
277 block = block.next();
278 }
279 return false;
280 }
281
updateEditorGOPATH()282 void GolangCode::updateEditorGOPATH()
283 {
284 if (m_gocodeCmd.isEmpty()) {
285 return;
286 }
287 QProcessEnvironment env = LiteApi::getCustomGoEnvironment(m_liteApp,m_liteApp->editorManager()->currentEditor());
288 QString gopathenv = env.value("GOPATH");
289 if (gopathenv != m_lastGopathEnv) {
290 m_lastGopathEnv = gopathenv;
291 gocodeUpdataLibpath(env);
292 //loadImportsList(env);
293 m_liteApp->appendLog("GolangCode",QString("gocode set lib-path \"%1\"").arg(gopathenv),false);
294 }
295 if (!m_gocodeImportProcess->isStop()) {
296 m_gocodeImportProcess->stop(10);
297 }
298 QStringList args;
299 args << "-f" << "csv" << "autocomplete" << "main.go" << "21";
300 m_gocodeImportProcess->setProcessEnvironment(env);
301 m_gocodeImportProcess->setWorkingDirectory(m_fileInfo.absolutePath());
302 m_gocodeImportProcess->startEx(m_gocodeCmd,args);
303 }
304
customGOPATHChanged(const QString &)305 void GolangCode::customGOPATHChanged(const QString &/*buildPath*/)
306 {
307 updateEditorGOPATH();
308 }
309
globalGOPATHChanged()310 void GolangCode::globalGOPATHChanged()
311 {
312 updateEditorGOPATH();
313 }
314
broadcast(QString,QString,QString)315 void GolangCode::broadcast(QString /*module*/,QString /*id*/,QString)
316 {
317 // if (module == "golangpackage" && id == "reloadgopath") {
318 // resetGocode();
319 // }
320 }
321
~GolangCode()322 GolangCode::~GolangCode()
323 {
324 delete m_gocodeProcess;
325 delete m_gocodeSetProcess;
326 delete m_importProcess;
327 delete m_gocodeImportProcess;
328 g_gocodeInstCount--;
329 if (g_gocodeInstCount == 0 && m_closeOnExit && !m_gocodeCmd.isEmpty()) {
330 ProcessEx::startDetachedExAndHide(m_gocodeCmd,QStringList() << "close");
331 }
332 }
333
gocodeUpdataLibpath(const QProcessEnvironment & env)334 void GolangCode::gocodeUpdataLibpath(const QProcessEnvironment &env)
335 {
336 if (m_gocodeCmd.isEmpty()) {
337 return;
338 }
339 m_gocodeProcess->setProcessEnvironment(env);
340 m_gocodeSetProcess->setProcessEnvironment(env);
341 if (!m_gocodeSetProcess->isStop()) {
342 m_gocodeSetProcess->stopAndWait(100,1000);
343 }
344 m_gocodeSetProcess->startEx(m_gocodeCmd,QStringList() << "set" << "lib-path" << env.value("GOPATH"));
345 }
346
gocodeReset(const QProcessEnvironment & env)347 void GolangCode::gocodeReset(const QProcessEnvironment &env)
348 {
349 if (m_gocodeCmd.isEmpty()) {
350 return;
351 }
352 m_gocodeProcess->setProcessEnvironment(env);
353 m_gocodeSetProcess->setProcessEnvironment(env);
354 if (!m_gocodeSetProcess->isStop()) {
355 m_gocodeSetProcess->stopAndWait(100,1000);
356 }
357 m_gocodeSetProcess->startEx(m_gocodeCmd,QStringList() << "close");
358 }
359
360
cgoComplete()361 void GolangCode::cgoComplete()
362 {
363 QStandardItem *root= m_completer->findRoot(m_preWord);
364 QStringList types;
365 types << "int" << "uint"
366 << "short" << "ushort"
367 << "char" << "schar" << "uchar"
368 << "long" << "ulong"
369 << "longlong" << "ulonglong"
370 << "float" << "double"
371 << "complexfloat" << "complexdouble";
372 QIcon icon = m_golangAst->iconFromTagEnum(LiteApi::TagType,true);
373 foreach(QString item, types) {
374 m_completer->appendChildItem(root,item,"type","",icon,true);
375 }
376 icon = m_golangAst->iconFromTagEnum(LiteApi::TagFunc,true);
377 m_completer->appendChildItem(root,"CString","func","func(string) *C.char",icon,true);
378 m_completer->appendChildItem(root,"GoString","func","func(*C.char) string",icon,true);
379 m_completer->appendChildItem(root,"GoStringN","func","func(*C.char, C.int) string",icon,true);
380 m_completer->appendChildItem(root,"GoBytes","func","func(unsafe.Pointer, C.int) []byte",icon,true);
381 m_completer->appendChildItem(root,"CBytes","func","func([]byte) unsafe.Pointer",icon,true);
382
383 QStringList all = parserCgoInEditor(1024);
384 icon = QIcon("icon:liteeditor/images/findword.png");
385 foreach (QString s, all) {
386 m_completer->appendChildItem(root,s,"","",icon,false);
387 }
388
389 m_completer->updateCompleterModel();
390 m_completer->showPopup();
391 }
392
parserCgoInEditor(int nmax)393 QStringList GolangCode::parserCgoInEditor(int nmax)
394 {
395 QTextCursor tc = m_editor->textCursor();
396 QTextDocument *doc = m_editor->document();
397 int maxNumber = tc.blockNumber();
398 int blockNumber = tc.blockNumber();
399 QTextBlock block = doc->firstBlock();
400
401 int first = maxNumber-nmax;
402 if (first > 0) {
403 block = doc->findBlockByNumber(first);
404 }
405 maxNumber += nmax;
406
407 QStringList all;
408 QRegExp rx("C\\.([\\w\\-\\_]+)");
409 while (block.isValid()) {
410 if (block.blockNumber() >= maxNumber) {
411 break;
412 }
413 if (block.blockNumber() == blockNumber) {
414 block = block.next();
415 continue;
416 }
417 QString line = block.text().trimmed();
418 if (!line.isEmpty()) {
419 int pos = 0;
420 while ((pos = rx.indexIn(line, pos)) != -1) {
421 QString cap = rx.cap(1);
422 all.push_back(cap);
423 pos += rx.matchedLength();
424 }
425 }
426 block = block.next();
427 }
428 all.removeDuplicates();
429 return all;
430 }
431
loadPkgList()432 void GolangCode::loadPkgList()
433 {
434 QString path = m_liteApp->resourcePath()+("/packages/go/pkglist");
435 QFile file(path);
436 if (file.open(QFile::ReadOnly)) {
437 QByteArray data = file.readAll();
438 QString ar = QString::fromUtf8(data);
439 ar.replace("\r\n","\n");
440 foreach(QString line, ar.split("\n")) {
441 line = line.trimmed();
442 if (line.isEmpty()) {
443 continue;
444 }
445 QStringList pathList = line.split("/");
446 m_pkgListMap.insert(pathList.last(),line);
447 m_importList.append(line);
448 }
449 }
450 m_importList.removeDuplicates();
451 m_importList << "github.com/"
452 << "golang.org/x/";
453 m_allImportList = m_importList;
454 }
455
loadImportsList(const QProcessEnvironment & env)456 void GolangCode::loadImportsList(const QProcessEnvironment &env)
457 {
458 if (!m_importProcess->isStop()) {
459 m_importProcess->stopAndWait(100,1000);
460 }
461
462 QString cmd = LiteApi::getGotools(m_liteApp);
463 if (cmd.isEmpty()) {
464 return;
465 }
466 QStringList args;
467 args << "pkgs" << "-list" << "-pkg" << "-skip_goroot";
468
469 m_importProcess->setProcessEnvironment(env);
470
471 m_importProcess->startEx(cmd,args);
472 }
473
currentEnvChanged(LiteApi::IEnv *)474 void GolangCode::currentEnvChanged(LiteApi::IEnv*)
475 {
476 QProcessEnvironment env = LiteApi::getGoEnvironment(m_liteApp);
477 // if (!LiteApi::hasGoEnv(env)) {
478 // return;
479 // }
480 m_liteApp->appendLog("GolangCode","go environment changed");
481 m_gobinCmd = FileUtil::lookupGoBin("go",m_liteApp,env,false);
482
483 m_gocodeCmd = FileUtil::lookupGoBin("gocode",m_liteApp,env,true);
484 if (m_gocodeCmd.isEmpty()) {
485 m_liteApp->appendLog("GolangCode","Could not find gocode (hint: is gocode installed?)",true);
486 } else {
487 m_liteApp->appendLog("GolangCode",QString("Found gocode at %1").arg(m_gocodeCmd));
488 }
489 m_gocodeProcess->setProcessEnvironment(env);
490 m_importProcess->setProcessEnvironment(env);
491 m_gocodeSetProcess->setProcessEnvironment(env);
492
493 gocodeReset(env);
494
495 currentEditorChanged(m_liteApp->editorManager()->currentEditor());
496 }
497
currentEditorChanged(LiteApi::IEditor * editor)498 void GolangCode::currentEditorChanged(LiteApi::IEditor *editor)
499 {
500 if (!editor) {
501 this->setCompleter(0);
502 return;
503 }
504
505 if (editor->mimeType() == "text/x-gosrc") {
506 LiteApi::ICompleter *completer = LiteApi::findExtensionObject<LiteApi::ICompleter*>(editor,"LiteApi.ICompleter");
507 this->setCompleter(completer);
508 } else if (editor->mimeType() == "browser/goplay") {
509 LiteApi::IEditor* pedit = LiteApi::findExtensionObject<LiteApi::IEditor*>(m_liteApp->extension(),"LiteApi.Goplay.IEditor");
510 if (pedit && pedit->mimeType() == "text/x-gosrc") {
511 editor = pedit;
512 LiteApi::ICompleter *completer = LiteApi::findExtensionObject<LiteApi::ICompleter*>(editor,"LiteApi.ICompleter");
513 this->setCompleter(completer);
514 }
515 } else {
516 this->setCompleter(0);
517 return;
518 }
519
520 m_editor = LiteApi::getTextEditor(editor);
521 if (!m_editor) {
522 return;
523 }
524 m_pkgImportTip->setWidget(editor->widget());
525 QString filePath = m_editor->filePath();
526 if (filePath.isEmpty()) {
527 return;
528 }
529 m_fileInfo.setFile(filePath);
530 m_gocodeProcess->setWorkingDirectory(m_fileInfo.absolutePath());
531
532 updateEditorGOPATH();
533 }
534
setCompleter(LiteApi::ICompleter * completer)535 void GolangCode::setCompleter(LiteApi::ICompleter *completer)
536 {
537 if (m_completer) {
538 disconnect(m_completer,0,this,0);
539 }
540 m_completer = completer;
541 if (m_completer) {
542 m_completer->setImportList(m_allImportList);
543 if (!m_gocodeCmd.isEmpty()) {
544 m_completer->setSearchSeparator(false);
545 m_completer->setExternalMode(true);
546 connect(m_completer,SIGNAL(prefixChanged(QTextCursor,QString,bool)),this,SLOT(prefixChanged(QTextCursor,QString,bool)));
547 connect(m_completer,SIGNAL(wordCompleted(QString,QString,QString)),this,SLOT(wordCompleted(QString,QString,QString)));
548 } else {
549 m_completer->setSearchSeparator(true);
550 m_completer->setExternalMode(false);
551 }
552 }
553 }
554
prefixChanged(QTextCursor cur,QString pre,bool force)555 void GolangCode::prefixChanged(QTextCursor cur,QString pre,bool force)
556 {
557 if (m_completer->completionContext() != LiteApi::CompleterCodeContext) {
558 return;
559 }
560
561 if (m_gocodeCmd.isEmpty()) {
562 return;
563 }
564 // if (m_completer->completer()->completionPrefix().startsWith(pre)) {
565 // // qDebug() << pre << m_completer->completer()->completionPrefix();
566 // // return;
567 // }
568 if (!m_gocodeProcess->isStop()) {
569 return;
570 }
571 int offset = -1;
572 if (pre.endsWith('.')) {
573 m_preWord = pre;
574 offset = 0;
575 } else if (pre.length() == m_completer->prefixMin()) {
576 m_preWord.clear();
577 } else {
578 if (!force) {
579 return;
580 }
581 m_preWord.clear();
582 int index = pre.lastIndexOf(".");
583 if (index != -1) {
584 m_preWord = pre.left(index);
585 }
586 }
587
588 m_prefix = pre;
589 m_lastPrefix = m_prefix;
590
591 if (!m_preWord.isEmpty()) {
592 m_completer->clearItemChilds(m_preWord);
593 }
594
595 if (m_preWord == "C.") {
596 cgoComplete();
597 return;
598 }
599 if (m_preWord.endsWith(".")) {
600 bool testDigit = true;
601 for (int i = 0; i < m_preWord.size()-1; i++) {
602 if (!m_preWord.at(i).isDigit()) {
603 testDigit = false;
604 break;
605 }
606 }
607 if (testDigit) {
608 return;
609 }
610 }
611 if (m_prefix.lastIndexOf("..") > 0) {
612 m_pkgImportTip->hide();
613 return;
614 }
615
616
617 QString src = cur.document()->toPlainText();
618 src = src.replace("\r\n","\n");
619 m_writeData = src.left(cur.position()).toUtf8();
620 QStringList args;
621 args << "-f" << "csv" << "autocomplete" << m_fileInfo.fileName() << QString::number(m_writeData.length()+offset);
622 m_writeData = src.toUtf8();
623 m_gocodeProcess->setWorkingDirectory(m_fileInfo.absolutePath());
624 m_gocodeProcess->startEx(m_gocodeCmd,args);
625 }
626
wordCompleted(QString,QString,QString)627 void GolangCode::wordCompleted(QString,QString,QString)
628 {
629 m_prefix.clear();
630 }
631
started()632 void GolangCode::started()
633 {
634 if (m_writeData.isEmpty()) {
635 m_gocodeProcess->closeWriteChannel();
636 return;
637 }
638 m_gocodeProcess->write(m_writeData);
639 m_gocodeProcess->closeWriteChannel();
640 m_writeData.clear();
641 }
642
finished(int code,QProcess::ExitStatus)643 void GolangCode::finished(int code,QProcess::ExitStatus)
644 {
645 if (code != 0) {
646 return;
647 }
648
649 if (m_prefix.isEmpty()) {
650 return;
651 }
652
653 if (m_prefix != m_lastPrefix) {
654 m_prefix.clear();
655 return;
656 }
657
658 QByteArray read = m_gocodeProcess->readAllStandardOutput();
659
660 QList<QByteArray> all = read.split('\n');
661 //func,,Fprint,,func(w io.Writer, a ...interface{}) (n int, error os.Error)
662 //type,,Formatter,,interface
663 //const,,ModeExclusive,,
664 //var,,Args,,[]string
665 int n = 0;
666 QIcon icon;
667 QStandardItem *root= m_completer->findRoot(m_preWord);
668 foreach (QByteArray bs, all) {
669 QStringList word = QString::fromUtf8(bs,bs.size()).split(",,");
670 //nsf/gocode count=3
671 //mdempsky/gocode count = 4
672 // ("var", "s4", "string", "")
673 // ("func", "Errorf", "func(format string, a ...interface{}) error", "fmt")
674 if (word.count() < 3) {
675 continue;
676 }
677 if (word.at(0) == "PANIC") {
678 continue;
679 }
680 LiteApi::ASTTAG_ENUM tag = LiteApi::TagNone;
681 QString kind = word.at(0);
682 QString info = word.at(2);
683 if (kind == "package") {
684 tag = LiteApi::TagPackage;
685 } else if (kind == "func") {
686 tag = LiteApi::TagFunc;
687 } else if (kind == "var") {
688 tag = LiteApi::TagValue;
689 } else if (kind == "const") {
690 tag = LiteApi::TagConst;
691 } else if (kind == "type") {
692 if (info == "interface") {
693 tag = LiteApi::TagInterface;
694 } else if (info == "struct") {
695 tag = LiteApi::TagStruct;
696 } else {
697 tag = LiteApi::TagType;
698 }
699 }
700
701 if (m_golangAst) {
702 icon = m_golangAst->iconFromTagEnum(tag,true);
703 }
704 //m_completer->appendItemEx(m_preWord+word.at(1),kind,info,icon,true);
705 m_completer->appendChildItem(root,word.at(1),kind,info,icon,true);
706 n++;
707 }
708 m_lastPrefix = m_prefix;
709 m_prefix.clear();
710 if (n >= 1) {
711 m_completer->updateCompleterModel();
712 m_completer->showPopup();
713 }
714 if (n == 0 && m_lastPrefix.endsWith(".")) {
715 QString id = m_lastPrefix.left(m_lastPrefix.length()-1);
716 QStringList pkgs = m_pkgListMap.values(id);
717 pkgs.sort();
718 if (m_allImportHint) {
719 QStringList extras = m_extraPkgListMap.values(id);
720 extras.sort();
721 pkgs << extras;
722 }
723 if (!pkgs.isEmpty() && !findImport(id)) {
724 QPlainTextEdit *ed = LiteApi::getPlainTextEdit(m_editor);
725 if (ed) {
726 int pos = ed->textCursor().position();
727 m_pkgImportTip->showPkgHint(pos,pkgs,ed);
728 }
729 }
730 }
731 }
732
gocodeImportStarted()733 void GolangCode::gocodeImportStarted()
734 {
735 m_gocodeImportProcess->write("package main\nimport \"\"");
736 m_gocodeImportProcess->closeWriteChannel();
737 }
738
gocodeImportFinished(int code,QProcess::ExitStatus)739 void GolangCode::gocodeImportFinished(int code, QProcess::ExitStatus)
740 {
741 if (code != 0) {
742 return;
743 }
744 QByteArray data = m_gocodeImportProcess->readAllStandardOutput();
745 QList<QString> lines = QString::fromUtf8(data).split('\n');
746
747 QStringList importList;
748 m_extraPkgListMap.clear();
749 foreach (QString line, lines) {
750 QStringList ar = line.split(",,");
751 if (ar.count() < 3) {
752 continue;
753 }
754 if (ar.at(0) == "PANIC") {
755 continue;
756 }
757 if (ar[0] != "import") {
758 continue;
759 }
760 if (m_importList.contains(ar[1])) {
761 continue;
762 }
763 QString pkg = ar[1];
764 importList.append(pkg);
765 QStringList pathList = pkg.split("/");
766 m_extraPkgListMap.insert(pathList.last(),pkg);
767 }
768 if (m_completer) {
769 m_completer->setImportList(QStringList() << m_importList << importList);
770 }
771 }
772
importFinished(int code,QProcess::ExitStatus)773 void GolangCode::importFinished(int code,QProcess::ExitStatus)
774 {
775 if (code != 0) {
776 return;
777 }
778 return;
779 QByteArray read = m_importProcess->readAllStandardOutput();
780 QString data = QString::fromUtf8(read);
781 QStringList importList = data.split('\n');
782 importList.removeDuplicates();
783 importList.sort();
784
785 m_extraPkgListMap.clear();
786 foreach (QString line, importList) {
787 QStringList pathList = line.split("/");
788 m_extraPkgListMap.insert(pathList.last(),line);
789 }
790
791 m_allImportList = m_importList;
792 m_allImportList.append(importList);
793 m_allImportList.removeDuplicates();
794
795 if (m_completer) {
796 m_completer->setImportList(m_allImportList);
797 }
798 }
799
ImportPkgTip(LiteApi::IApplication * app,QObject * parent)800 ImportPkgTip::ImportPkgTip(LiteApi::IApplication *app, QObject *parent)
801 : QObject(parent), m_liteApp(app)
802 {
803 m_editWidget = 0;
804 m_startPos = 0;
805 m_pkgIndex = 0;
806 m_escapePressed = false;
807 m_enterPressed = false;
808 m_popup = new FakeToolTip();
809 //m_popup->setFocusPolicy(Qt::NoFocus);
810 m_infoLabel = new QLabel;
811 m_pkgLabel = new QLabel;
812 QHBoxLayout *layout = new QHBoxLayout;
813 layout->setMargin(0);
814 layout->addWidget(m_infoLabel);
815 layout->addWidget(m_pkgLabel);
816 m_popup->setLayout(layout);
817
818 qApp->installEventFilter(this);
819 }
820
~ImportPkgTip()821 ImportPkgTip::~ImportPkgTip()
822 {
823 delete m_popup;
824 }
825
showPkgHint(int startpos,const QStringList & pkg,QPlainTextEdit * ed)826 void ImportPkgTip::showPkgHint(int startpos, const QStringList &pkg, QPlainTextEdit *ed)
827 {
828 const QDesktopWidget *desktop = QApplication::desktop();
829 #ifdef Q_WS_MAC
830 const QRect screen = desktop->availableGeometry(desktop->screenNumber(ed));
831 #else
832 const QRect screen = desktop->screenGeometry(desktop->screenNumber(ed));
833 #endif
834 m_pkg = pkg;
835 m_startPos = startpos;
836 m_enterPressed = false;
837 m_escapePressed = false;
838 m_pkgIndex = 0;
839 const QSize sz = m_popup->minimumSizeHint();
840 QTextCursor cur = ed->textCursor();
841 cur.setPosition(startpos);
842 QPoint pos = ed->cursorRect(cur).topLeft();
843 pos.setY(pos.y() - sz.height() - 1);
844 pos = ed->mapToGlobal(pos);
845 if (pos.x() + sz.width() > screen.right())
846 pos.setX(screen.right() - sz.width());
847 m_infoLabel->setText(tr("warning, pkg not find, please enter to import :"));
848 if (m_pkg.size() == 1) {
849 m_pkgLabel->setText(m_pkg[0]);
850 } else {
851 m_pkgLabel->setText(QString("[%1/%2] \"%3\"").arg(m_pkgIndex+1).arg(m_pkg.size()).arg(m_pkg[m_pkgIndex]));
852 }
853 m_popup->move(pos);
854 if (!m_popup->isVisible()) {
855 m_popup->show();
856 }
857 }
858
hide()859 void ImportPkgTip::hide()
860 {
861 m_popup->hide();
862 }
863
setWidget(QWidget * widget)864 void ImportPkgTip::setWidget(QWidget *widget)
865 {
866 hide();
867 m_editWidget = widget;
868 }
869
eventFilter(QObject * obj,QEvent * e)870 bool ImportPkgTip::eventFilter(QObject *obj, QEvent *e)
871 {
872 if (!m_popup->isVisible()) {
873 return QObject::eventFilter(obj,e);
874 }
875 switch (e->type()) {
876 case QEvent::ShortcutOverride:
877 if (m_popup->isVisible() && static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) {
878 m_escapePressed = true;
879 e->accept();
880 } else if (static_cast<QKeyEvent*>(e)->modifiers() & Qt::ControlModifier) {
881 m_popup->hide();
882 }
883 break;
884 case QEvent::KeyPress: {
885 QKeyEvent *ke = static_cast<QKeyEvent*>(e);
886 if (ke->key() == Qt::Key_Escape) {
887 m_escapePressed = true;
888 } else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
889 m_enterPressed = true;
890 e->accept();
891 return true;
892 } else if (ke->key() == Qt::Key_Up) {
893 if (m_pkg.size() > 1) {
894 e->accept();
895 m_pkgIndex--;
896 if (m_pkgIndex < 0) {
897 m_pkgIndex = m_pkg.size()-1;
898 }
899 m_pkgLabel->setText(QString("[%1/%2] \"%3\"").arg(m_pkgIndex+1).arg(m_pkg.size()).arg(m_pkg[m_pkgIndex]));
900 }
901 return true;
902 } else if (ke->key() == Qt::Key_Down) {
903 if (m_pkg.size() > 1) {
904 e->accept();
905 m_pkgIndex++;
906 if (m_pkgIndex >= m_pkg.size()) {
907 m_pkgIndex = 0;
908 }
909 m_pkgLabel->setText(QString("[%1/%2] \"%3\"").arg(m_pkgIndex+1).arg(m_pkg.size()).arg(m_pkg[m_pkgIndex]));
910 }
911 return true;
912 }
913 }
914 break;
915 case QEvent::KeyRelease: {
916 QKeyEvent *ke = static_cast<QKeyEvent*>(e);
917 if (ke->key() == Qt::Key_Escape && m_escapePressed) {
918 hide();
919 } else if ( (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) &&
920 m_enterPressed) {
921 e->accept();
922 m_enterPressed = false;
923 hide();
924 emit import(m_pkg[m_pkgIndex],m_startPos);
925 } else if (ke->key() == Qt::Key_Up) {
926 return true;
927 } else if (ke->key() == Qt::Key_Down) {
928 return true;
929 } else if (ke->text() != "."){
930 hide();
931 }
932 }
933 break;
934 case QEvent::FocusOut:
935 case QEvent::WindowDeactivate:
936 case QEvent::Resize:
937 if (obj != m_editWidget)
938 break;
939 hide();
940 break;
941 case QEvent::Move:
942 if (obj != m_liteApp->mainWindow())
943 break;
944 hide();
945 break;
946 case QEvent::MouseButtonPress:
947 case QEvent::MouseButtonRelease:
948 case QEvent::MouseButtonDblClick:
949 case QEvent::Wheel: {
950 hide();
951 }
952 break;
953 default:
954 break;
955 }
956 return false;
957 }
958