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