completion.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /*
  2. ktigcc - TIGCC IDE for KDE
  3. Copyright (C) 2006 Kevin Kofler
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2, or (at your option)
  7. any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software Foundation,
  14. Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. #include <qstring.h>
  17. #include <qvaluelist.h>
  18. #include <qpair.h>
  19. #include <qpoint.h>
  20. #include <qregexp.h>
  21. #include <qfileinfo.h>
  22. #include <qdir.h>
  23. #include <kmessagebox.h>
  24. #include <kate/view.h>
  25. #include <kate/document.h>
  26. #include <ktexteditor/editinterfaceext.h>
  27. #include <kconfig.h>
  28. #include "completion.h"
  29. #include "parsing.h"
  30. #include "preferences.h"
  31. #include "mainform.h"
  32. #include "tpr.h"
  33. // Maps file name to a CompletionInfo.
  34. QMap<QString,CompletionInfo> systemHeaderCompletion, projectCompletion;
  35. static void resetSearchedFlags(void)
  36. {
  37. for (QMap<QString,CompletionInfo>::Iterator it=projectCompletion.begin();
  38. it!=projectCompletion.end(); ++it)
  39. (*it).searched=false;
  40. for (QMap<QString,CompletionInfo>::Iterator it=systemHeaderCompletion.begin();
  41. it!=systemHeaderCompletion.end(); ++it)
  42. (*it).searched=false;
  43. }
  44. static void findSymbolInSystemHeaders(const QString &symbol,
  45. const QStringList &systemHeaders,
  46. QString &symbolFile,
  47. unsigned &symbolLine,
  48. bool &systemHeader)
  49. {
  50. for (QStringList::ConstIterator it=systemHeaders.begin();
  51. it!=systemHeaders.end(); ++it) {
  52. const QString &headerName=*it;
  53. // Avoid infinite recursion.
  54. if (systemHeaderCompletion.contains(headerName)
  55. && !systemHeaderCompletion[headerName].searched) {
  56. CompletionInfo &completionInfo=systemHeaderCompletion[headerName];
  57. completionInfo.searched=true;
  58. if (completionInfo.lineNumbers.contains(symbol)) {
  59. symbolFile=headerName;
  60. symbolLine=completionInfo.lineNumbers[symbol];
  61. systemHeader=true;
  62. return;
  63. } else {
  64. findSymbolInSystemHeaders(symbol,completionInfo.includedSystem,
  65. symbolFile,symbolLine,systemHeader);
  66. if (!symbolFile.isNull()) return;
  67. }
  68. }
  69. }
  70. }
  71. static bool findSymbolInFileRecursive(const QString &symbol,
  72. const QString &fileText,
  73. const QString &fileName,
  74. MainForm *mainForm,
  75. QString &symbolFile,
  76. unsigned &symbolLine,
  77. bool &systemHeader)
  78. {
  79. symbolFile=QString::null;
  80. systemHeader=false;
  81. if (!projectCompletion.contains(fileName) || projectCompletion[fileName].dirty) {
  82. QFileInfo fileInfo(fileName);
  83. QString pathInProject=fileInfo.isRelative()?fileInfo.dirPath():".";
  84. CompletionInfo completionInfo=parseFileCompletion(fileText,pathInProject);
  85. if (completionInfo.dirty) return false;
  86. projectCompletion.insert(fileName,completionInfo);
  87. }
  88. CompletionInfo &completionInfo=projectCompletion[fileName];
  89. // Avoid infinite recursion.
  90. if (completionInfo.searched) return true;
  91. completionInfo.searched=true;
  92. if (completionInfo.lineNumbers.contains(symbol)) {
  93. symbolFile=fileName;
  94. symbolLine=completionInfo.lineNumbers[symbol];
  95. return true;
  96. }
  97. for (QStringList::ConstIterator it=completionInfo.included.begin();
  98. it!=completionInfo.included.end(); ++it) {
  99. const QString &headerName=*it;
  100. QString headerText=mainForm->textForHeader(headerName);
  101. if (!headerText.isNull()) {
  102. if (!findSymbolInFile(symbol,headerText,headerName,mainForm,symbolFile,
  103. symbolLine,systemHeader))
  104. return false;
  105. if (!symbolFile.isNull()) return true;
  106. }
  107. }
  108. findSymbolInSystemHeaders(symbol,completionInfo.includedSystem,symbolFile,
  109. symbolLine,systemHeader);
  110. return true;
  111. }
  112. bool findSymbolInFile(const QString &symbol,
  113. const QString &fileText,
  114. const QString &fileName,
  115. MainForm *mainForm,
  116. QString &symbolFile,
  117. unsigned &symbolLine,
  118. bool &systemHeader)
  119. {
  120. resetSearchedFlags();
  121. return findSymbolInFileRecursive(symbol,fileText,fileName,mainForm,symbolFile,
  122. symbolLine,systemHeader);
  123. }
  124. static void mergeCompletionEntries(QValueList<KTextEditor::CompletionEntry> &dest,
  125. const QValueList<KTextEditor::CompletionEntry> &src)
  126. {
  127. for (QValueList<KTextEditor::CompletionEntry>::ConstIterator it=src.begin();
  128. it!=src.end(); ++it)
  129. dest.append(*it);
  130. }
  131. static void completionEntriesForSystemHeaders(const QStringList &systemHeaders,
  132. QValueList<KTextEditor::CompletionEntry> &result)
  133. {
  134. for (QStringList::ConstIterator it=systemHeaders.begin();
  135. it!=systemHeaders.end(); ++it) {
  136. const QString &headerName=*it;
  137. // Avoid infinite recursion.
  138. if (systemHeaderCompletion.contains(headerName)
  139. && !systemHeaderCompletion[headerName].searched) {
  140. CompletionInfo &completionInfo=systemHeaderCompletion[headerName];
  141. completionInfo.searched=true;
  142. mergeCompletionEntries(result,completionInfo.entries);
  143. completionEntriesForSystemHeaders(completionInfo.includedSystem,result);
  144. }
  145. }
  146. }
  147. static bool completionEntriesForFileRecursive(const QString &fileText,
  148. const QString &fileName,
  149. MainForm *mainForm,
  150. QValueList<KTextEditor::CompletionEntry> &result)
  151. {
  152. if (!projectCompletion.contains(fileName) || projectCompletion[fileName].dirty) {
  153. QFileInfo fileInfo(fileName);
  154. QString pathInProject=fileInfo.isRelative()?fileInfo.dirPath():".";
  155. CompletionInfo completionInfo=parseFileCompletion(fileText,pathInProject);
  156. if (completionInfo.dirty) return false;
  157. projectCompletion.insert(fileName,completionInfo);
  158. }
  159. CompletionInfo &completionInfo=projectCompletion[fileName];
  160. // Avoid infinite recursion.
  161. if (completionInfo.searched) return true;
  162. completionInfo.searched=true;
  163. mergeCompletionEntries(result,completionInfo.entries);
  164. completionEntriesForSystemHeaders(completionInfo.includedSystem,result);
  165. for (QStringList::ConstIterator it=completionInfo.included.begin();
  166. it!=completionInfo.included.end(); ++it) {
  167. const QString &headerName=*it;
  168. QString headerText=mainForm->textForHeader(headerName);
  169. if (!headerText.isNull())
  170. if (!completionEntriesForFile(headerText,headerName,mainForm,result))
  171. return false;
  172. }
  173. return true;
  174. }
  175. bool completionEntriesForFile(const QString &fileText,
  176. const QString &fileName,
  177. MainForm *mainForm,
  178. QValueList<KTextEditor::CompletionEntry> &result)
  179. {
  180. resetSearchedFlags();
  181. return completionEntriesForFileRecursive(fileText,fileName,mainForm,result);
  182. }
  183. static QValueList<KTextEditor::CompletionEntry> sortCompletionEntries(
  184. const QValueList<KTextEditor::CompletionEntry> &entries)
  185. {
  186. QMap<QString,QValueList<KTextEditor::CompletionEntry> > map;
  187. for (QValueList<KTextEditor::CompletionEntry>::ConstIterator it=entries.begin();
  188. it!=entries.end(); ++it) {
  189. const KTextEditor::CompletionEntry &entry=*it;
  190. map[entry.text].append(entry);
  191. }
  192. QValueList<KTextEditor::CompletionEntry> result;
  193. for (QMap<QString,QValueList<KTextEditor::CompletionEntry> >::ConstIterator
  194. it=map.begin(); it!=map.end(); ++it)
  195. mergeCompletionEntries(result,*it);
  196. return result;
  197. }
  198. bool parseHelpSources(QWidget *parent, const QString &directory,
  199. QMap<QString,CompletionInfo> &sysHdrCompletion)
  200. {
  201. return true; // TODO
  202. }
  203. bool parseSystemHeaders(QWidget *parent, const QString &directory,
  204. QMap<QString,CompletionInfo> &sysHdrCompletion)
  205. {
  206. QDir qdir(directory);
  207. QStringList headers=qdir.entryList("*.h",QDir::Files);
  208. for (QStringList::ConstIterator it=headers.begin(); it!=headers.end(); ++it) {
  209. const QString &header=*it;
  210. QString fileText=loadFileText(QFileInfo(qdir,header).filePath());
  211. if (fileText.isNull()) {
  212. KMessageBox::error(parent,QString("Can't open \'%1\'.").arg(header));
  213. return false;
  214. }
  215. sysHdrCompletion[header]=parseFileCompletion(fileText,QString::null);
  216. if (sysHdrCompletion[header].dirty) return false;
  217. }
  218. return true;
  219. }
  220. void loadSystemHeaderCompletion(void)
  221. {
  222. KConfig config("ktigcc/completion",true,false,"data");
  223. QStringList groupList=config.groupList();
  224. systemHeaderCompletion.clear();
  225. for (QStringList::ConstIterator it=groupList.begin(); it!=groupList.end(); ++it) {
  226. const QString &key=*it;
  227. if (key.endsWith(" Lines")) continue;
  228. CompletionInfo completionInfo;
  229. config.setGroup(key);
  230. completionInfo.includedSystem=config.readListEntry("Included");
  231. unsigned numEntries=config.readUnsignedNumEntry("Num Entries");
  232. for (unsigned i=0; i<numEntries; i++) {
  233. KTextEditor::CompletionEntry entry;
  234. entry.type=config.readEntry(QString("Entry %1 Type").arg(i));
  235. entry.text=config.readEntry(QString("Entry %1 Text").arg(i));
  236. entry.prefix=config.readEntry(QString("Entry %1 Prefix").arg(i));
  237. entry.postfix=config.readEntry(QString("Entry %1 Postfix").arg(i));
  238. entry.comment=config.readEntry(QString("Entry %1 Comment").arg(i));
  239. entry.userdata=config.readEntry(QString("Entry %1 User Data").arg(i));
  240. completionInfo.entries.append(entry);
  241. }
  242. QMap<QString,QString> entryMap=config.entryMap(key+" Lines");
  243. for (QMap<QString,QString>::ConstIterator it=entryMap.begin();
  244. it!=entryMap.end(); ++it)
  245. completionInfo.lineNumbers.insert(it.key(),(*it).toUInt());
  246. systemHeaderCompletion.insert(key,completionInfo);
  247. }
  248. }
  249. void saveSystemHeaderCompletion(void)
  250. {
  251. KConfig config("ktigcc/completion",false,false,"data");
  252. for (QMap<QString,CompletionInfo>::ConstIterator it=systemHeaderCompletion.begin();
  253. it!=systemHeaderCompletion.end(); ++it) {
  254. const QString &key=it.key();
  255. const CompletionInfo &completionInfo=*it;
  256. config.setGroup(key);
  257. config.writeEntry("Included",completionInfo.includedSystem);
  258. unsigned i=0;
  259. for (QValueList<KTextEditor::CompletionEntry>::ConstIterator it
  260. =completionInfo.entries.begin(); it!=completionInfo.entries.end();
  261. ++it, i++) {
  262. const KTextEditor::CompletionEntry &entry=*it;
  263. config.writeEntry(QString("Entry %1 Type").arg(i),entry.type);
  264. config.writeEntry(QString("Entry %1 Text").arg(i),entry.text);
  265. config.writeEntry(QString("Entry %1 Prefix").arg(i),entry.prefix);
  266. config.writeEntry(QString("Entry %1 Postfix").arg(i),entry.postfix);
  267. config.writeEntry(QString("Entry %1 Comment").arg(i),entry.comment);
  268. config.writeEntry(QString("Entry %1 User Data").arg(i),entry.userdata);
  269. }
  270. config.writeEntry("Num Entries",i);
  271. config.setGroup(key+" Lines");
  272. for (QMap<QString,unsigned>::ConstIterator it=completionInfo.lineNumbers.begin();
  273. it!=completionInfo.lineNumbers.end(); ++it)
  274. config.writeEntry(it.key(),*it);
  275. }
  276. config.sync();
  277. }
  278. TemplatePopup::TemplatePopup(Kate::View *parent)
  279. : QPopupMenu(parent), view(parent)
  280. {
  281. connect(this,SIGNAL(activated(int)),this,SLOT(QPopupMenu_activated(int)));
  282. unsigned i=0;
  283. for (QValueList<QPair<QString,QString> >::ConstIterator it=preferences.templates.begin();
  284. it!=preferences.templates.end(); ++it, i++)
  285. insertItem((*it).first,i);
  286. QPoint pos=parent->cursorCoordinates();
  287. if (pos.x()<0 || pos.y()<0) {
  288. // Cursor outside of the view, so center on view instead.
  289. QSize parentSize=parent->size();
  290. QSize popupSize=sizeHint();
  291. pos.setX((parentSize.width()-popupSize.width())>>1);
  292. pos.setY((parentSize.height()-popupSize.height())>>1);
  293. }
  294. exec(parent->mapToGlobal(pos));
  295. deleteLater();
  296. }
  297. void TemplatePopup::QPopupMenu_activated(int id)
  298. {
  299. QString code=preferences.templates[id].second;
  300. QString indent=view->currentTextLine();
  301. // Remove everything starting from the first non-whitespace character.
  302. indent=indent.remove(QRegExp("(?!\\s).*$"));
  303. indent.prepend('\n');
  304. code.replace('\n',indent);
  305. int cursorPos=code.find('|');
  306. if (cursorPos>=0) {
  307. QString left=code.left(cursorPos);
  308. QString right=code.mid(cursorPos+1);
  309. unsigned row, col;
  310. KTextEditor::EditInterfaceExt *editExt=KTextEditor::editInterfaceExt(view->getDoc());
  311. editExt->editBegin();
  312. view->insertText(left);
  313. view->cursorPositionReal(&row,&col);
  314. view->insertText(right);
  315. editExt->editEnd();
  316. view->setCursorPositionReal(row,col);
  317. } else view->insertText(code);
  318. }
  319. CompletionPopup::CompletionPopup(Kate::View *parent, const QString &fileName,
  320. MainForm *mainForm, QObject *receiver)
  321. : QObject(parent)
  322. {
  323. connect(this,SIGNAL(closed()),receiver,SLOT(completionPopup_closed()));
  324. QValueList<KTextEditor::CompletionEntry> entries;
  325. if (!completionEntriesForFile(parent->getDoc()->text(),fileName,mainForm,
  326. entries)) {
  327. emit closed();
  328. deleteLater();
  329. return;
  330. }
  331. entries=sortCompletionEntries(entries);
  332. unsigned column=parent->cursorColumnReal();
  333. int offset=0;
  334. if (column) {
  335. QString textLine=parent->currentTextLine();
  336. if (column<=textLine.length()) {
  337. while (column && (textLine[--column].isLetterOrNumber()
  338. || textLine[column]=='_' || textLine[column]=='$'))
  339. offset++;
  340. }
  341. }
  342. connect(parent,SIGNAL(completionAborted()),this,SLOT(deleteLater()));
  343. connect(parent,SIGNAL(completionAborted()),this,SIGNAL(closed()));
  344. connect(parent,SIGNAL(completionDone()),this,SLOT(deleteLater()));
  345. connect(parent,SIGNAL(completionDone()),this,SIGNAL(closed()));
  346. parent->showCompletionBox(entries,offset);
  347. }