/* Copyright (C) 2005 by Nicolas Escuder Copyright (C) 2001 by smeier@kdevelop.org This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public version 2, License as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "phpcodecompletion.h" #include "phpsupportpart.h" #include "phpconfigdata.h" #include #include #include #include #include #include #include #include #include "phpfile.h" using namespace std; PHPCodeCompletion::PHPCodeCompletion(PHPSupportPart *phpSupport, PHPConfigData *config) : QObject(), m_cursorInterface(0), m_codeInterface(0), m_editInterface(0), m_selectionInterface(0) { m_phpSupport = phpSupport; m_config = config; m_model = phpSupport->codeModel(); m_argWidgetShow = false; m_completionBoxShow = false; readGlobalPHPFunctionsFile(); } PHPCodeCompletion::~PHPCodeCompletion(){ } void PHPCodeCompletion::readGlobalPHPFunctionsFile(){ KStandardDirs *dirs = PHPSupportFactory::instance()->dirs(); QString phpFuncFile = dirs->findResource("data","kdevphpsupport/phpfunctions"); QRegExp lineReg(":([0-9A-Za-z_]+) ([0-9A-Za-z_]+)\\((.*)\\)"); FunctionCompletionEntry e; QFile f(phpFuncFile); if ( f.open(IO_ReadOnly) ) { // file opened successfully QTextStream t( &f ); // use a text stream QString s; while ( !t.eof() ) { // until end of file... s = t.readLine(); // line of text excluding '\n' if (lineReg.search(s.local8Bit()) != -1) { e.prefix = lineReg.cap(1); e.text = lineReg.cap(2); e.postfix = "(" + QString(lineReg.cap(3)) + ")"; e.prototype = QString(lineReg.cap(1)) + " " + QString(lineReg.cap(2)) + "(" + QString(lineReg.cap(3)) + ")"; m_globalFunctions.append(e); } } f.close(); } } void PHPCodeCompletion::argHintHided(){ kdDebug(9018) << "PHPCodeCompletion::argHintHided" << endl; m_argWidgetShow = false; } void PHPCodeCompletion::completionBoxHided(){ kdDebug(9018) << "PHPCodeCompletion::completionBoxHided()" << endl; m_completionBoxShow = false; } void PHPCodeCompletion::setActiveEditorPart(KParts::Part *part) { if (!part || !part->widget()) return; kdDebug(9018) << "PHPCodeCompletion::setActiveEditorPart" << endl; if (!(m_config->getCodeCompletion() || m_config->getCodeHinting())) return; // no help m_editInterface = dynamic_cast(part); if (!m_editInterface) { kdDebug(9018) << "editor doesn't support the EditDocumentIface" << endl; return; } m_cursorInterface = dynamic_cast(part->widget()); if (!m_cursorInterface) { kdDebug(9018) << "editor does not support the ViewCursorInterface" << endl; return; } m_codeInterface = dynamic_cast(part->widget()); if (!m_codeInterface) { // no CodeCompletionDocument available kdDebug(9018) << "editor doesn't support the CodeCompletionDocumentIface" << endl; return; } m_selectionInterface = dynamic_cast(part); if (!m_selectionInterface) { kdDebug(9018) << "editor doesn't support the SelectionInterface" << endl; return; } disconnect(part->widget(), 0, this, 0 ); // to make sure that it is't connected twice // connect(part->widget(), SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChanged())); connect( part, SIGNAL(textChanged()), this, SLOT(cursorPositionChanged()) ); connect(part->widget(), SIGNAL(argHintHidden()), this, SLOT(argHintHided())); connect(part->widget(), SIGNAL(completionAborted()), this, SLOT(completionBoxHided())); connect(part->widget(), SIGNAL(completionDone()), this, SLOT(completionBoxHided())); } void PHPCodeCompletion::cursorPositionChanged(){ uint line, col; if( !m_cursorInterface || !m_selectionInterface || !m_codeInterface || !m_editInterface ) return; m_cursorInterface->cursorPositionReal(&line, &col); kdDebug(9018) << "cursorPositionChanged:" << line << ":" << col << endl; m_currentLine = line; QString lineStr = m_editInterface->textLine(line); if (lineStr.isNull() || lineStr.isEmpty()) { return; } if (m_selectionInterface->hasSelection()) { kdDebug(9018) << "No CodeCompletion/ArgHinting at the moment, because text is selected" << endl; return; } if (m_config->getCodeHinting()) { int pos1 = lineStr.findRev("(", col - 1); int pos2 = lineStr.findRev(QRegExp("[ \\t=;\\$\\.\\(\\)]"), pos1 - 1); int pos3 = lineStr.findRev(")", col); if (pos1 > pos2 && pos1 != -1 && pos3 < pos1) { QString line = lineStr.mid(pos2 + 1, pos1 - pos2 - 1).stripWhiteSpace(); checkForArgHint(line, col); kdDebug(9018) << "end checkForArgHint" << endl; } } if (m_config->getCodeCompletion()) { if (m_completionBoxShow == true) { return; } int pos = lineStr.findRev(QRegExp("[ \\t=;\\$\\.\\(\\)]"), col - 1); QString line = lineStr.mid(pos + 1, col - pos).stripWhiteSpace(); if (checkForVariable(line, col)) { kdDebug(9018) << "end checkForVariable" << endl; return; } if (checkForStaticFunction(line, col)) { kdDebug(9018) << "end checkForStaticFunction" << endl; return; } if(checkForGlobalFunction(line, col)) { kdDebug(9018) << "end checkForGlobalFunction" << endl; return; } pos = lineStr.stripWhiteSpace().findRev(QRegExp("[ \\t=;\\$\\.\\(\\)]"), col - 1); line = lineStr.mid(pos + 1, col - pos); if (checkForNew(line, col)) { kdDebug(9018) << "end checkForNew" << endl; return; } if (checkForExtends(line, col)) { kdDebug(9018) << "end checkForExtends" << endl; return; } kdDebug(9018) << "end checkFor" << endl; } } bool PHPCodeCompletion::showCompletionBox(QValueList list, unsigned long max) { if (list.count() > 0) { if (list.count() == 1) { KTextEditor::CompletionEntry e = list.first(); if (e.text.length() == max) return false; } m_completionBoxShow = true; m_codeInterface->showCompletionBox(list, max, FALSE); return true; } return false; } bool PHPCodeCompletion::checkForStaticFunction(QString line, int col) { kdDebug(9018) << "checkForStaticFunction" << endl; QValueList list; if (line.find("::") == -1) return false; QRegExp Class("([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)::([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*|)"); Class.setCaseSensitive(FALSE); if (Class.search(line) != -1) { QString classname = Class.cap(1); QString function = Class.cap(2); ClassList classList = getClassByName(classname); ClassList::Iterator classIt; for (classIt = classList.begin(); classIt != classList.end(); ++classIt) { ClassDom nClass = *classIt; FunctionList funcList = nClass->functionList(); FunctionList::Iterator funcIt; for (funcIt = funcList.begin(); funcIt != funcList.end(); ++funcIt) { FunctionDom nFunc = *funcIt; if ((function.isEmpty() || nFunc->name().startsWith(function, FALSE)) && nFunc->isStatic()) { KTextEditor::CompletionEntry e; e.prefix = nClass->name() + " ::"; e.text = nFunc->name(); ArgumentDom pArg = (*funcIt)->argumentList().first(); if (pArg) e.postfix = "(" + pArg->type() +")"; else e.postfix = "()"; list.append(e); } } if (nClass->baseClassList().count() != 0) { QStringList base = nClass->baseClassList(); QStringList::Iterator nameIt; for (nameIt = base.begin(); nameIt != base.end(); ++nameIt) { ClassList baseList = getClassByName(*nameIt); ClassList::Iterator baseIt; for (baseIt = baseList.begin(); baseIt != baseList.end(); ++baseIt) classList.append(*baseIt); } } } return showCompletionBox(list, Class.cap(2).length()); } return false; } bool PHPCodeCompletion::checkForNew(QString line, int col){ kdDebug(9018) << "checkForNew" << endl; QValueList list; if (line.find("new ", 0, FALSE) == -1) return false; QRegExp New("[& \t]*new[ \t]+([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*|)"); New.setCaseSensitive(FALSE); if (New.search(line) != -1) { list = getClasses( New.cap(1) ); if (New.cap(1).lower() == "ob") { KTextEditor::CompletionEntry e; e.text = "object"; list.append(e); } if (New.cap(1).lower() == "ar") { KTextEditor::CompletionEntry e; e.text = "array"; list.append(e); } return showCompletionBox(list, New.cap(1).length()); } return false; } bool PHPCodeCompletion::checkForExtends(QString line, int col){ kdDebug(9018) << "checkForExtends" << endl; QValueList list; if (line.find("extends", 0, FALSE) == -1) return false; QRegExp extends("[ \t]*extends[ \t]+([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*|)"); extends.setCaseSensitive(FALSE); if (extends.search(line) != -1) { list = getClasses(extends.cap(1)); return showCompletionBox(list, extends.cap(1).length()); } return false; } bool PHPCodeCompletion::checkForVariable(QString line, int col){ kdDebug(9018) << "checkForVariable" << endl; QValueList list; QString args; if (line.find("->") == -1) { return false; } if (line.left(2) != "->") { int pos = line.findRev("->"); args = line.mid(pos + 2, line.length() - pos); line = line.mid(0, pos); } QStringList vars = QStringList::split("->", line); QString classname; for ( QStringList::Iterator it = vars.begin(); it != vars.end(); ++it ) { classname = getClassName(*it, classname); } if (classname.isEmpty()) { return false; } this->setStatusBar(line, classname); list = this->getFunctionsAndVars(classname, args); return showCompletionBox(list, args.length()); } bool PHPCodeCompletion::checkForGlobalFunction(QString line, int col) { kdDebug(9018) << "checkForGlobalFunction(" + line + "," << col << endl; QValueList list; if (line.length() < 3) return false; list = this->getFunctionsAndVars("", line); return showCompletionBox(list, line.length()); } QValueList PHPCodeCompletion::getClasses(QString name) { QValueList list; QStringList added; ClassList classList = m_model->globalNamespace()->classList(); ClassList::Iterator classIt; for (classIt = classList.begin(); classIt != classList.end(); ++classIt) { ClassDom nClass = *classIt; if (name == NULL || name.isEmpty() || nClass->name().startsWith(name, FALSE)) { KTextEditor::CompletionEntry e; QStringList::Iterator it = added.find(nClass->name()); if (it == added.end()) { e.text = nClass->name(); list.append(e); added.append(nClass->name()); } } } return list; } QValueList PHPCodeCompletion::getFunctionsAndVars(QString classname, QString function) { kdDebug(9018) << "getFunctionsAndVars " << classname << endl; QValueList list; if (classname.isEmpty()) { QValueList::Iterator it; for( it = m_globalFunctions.begin(); it != m_globalFunctions.end(); ++it ) { if((*it).text.startsWith(function, FALSE)){ KTextEditor::CompletionEntry e; e = (*it); list.append(e); } } FunctionList methodList = m_model->globalNamespace()->functionList(); FunctionList::Iterator methodIt; for (methodIt = methodList.begin(); methodIt != methodList.end(); ++methodIt) { if ((*methodIt)->name().startsWith(function, FALSE)){ KTextEditor::CompletionEntry e; e.text = (*methodIt)->name(); ArgumentDom pArg = (*methodIt)->argumentList().first(); if (pArg) e.postfix = "(" + pArg->type() +")"; else e.postfix = "()"; list.append(e); } } return list; } ClassList classList = getClassByName(classname); ClassList::Iterator classIt; for (classIt = classList.begin(); classIt != classList.end(); ++classIt) { ClassDom nClass = *classIt; FunctionList methodList = nClass->functionList(); FunctionList::Iterator methodIt; for (methodIt = methodList.begin(); methodIt != methodList.end(); ++methodIt) { FunctionDom pMethod = *methodIt; if (function.isEmpty() || pMethod->name().startsWith(function, FALSE)) { KTextEditor::CompletionEntry e; ArgumentDom arg = pMethod->argumentList().first(); e.prefix = nClass->name() + " ::"; e.text = pMethod->name(); e.postfix = "(" + arg->type() + ")"; list.append(e); } } VariableList attrList = nClass->variableList(); VariableList::Iterator attrIt; for (attrIt = attrList.begin(); attrIt != attrList.end(); ++attrIt) { VariableDom pVar = *attrIt; if (function.isEmpty() || pVar->name().startsWith(function, FALSE)) { KTextEditor::CompletionEntry e; e.prefix = nClass->name() + " ::"; e.text = pVar->name(); e.postfix = ""; list.append(e); } } if (nClass->baseClassList().count() != 0) { QStringList base = nClass->baseClassList(); QStringList::Iterator nameIt; for (nameIt = base.begin(); nameIt != base.end(); ++nameIt) { ClassList baseList = getClassByName(*nameIt); ClassList::Iterator baseIt; for (baseIt = baseList.begin(); baseIt != baseList.end(); ++baseIt) classList.append(*baseIt); } } } return list; } QStringList PHPCodeCompletion::getArguments(QString classname, QString function) { kdDebug(9018) << "getArguments " << function << endl; QStringList list; if (classname.isEmpty()) { QValueList::Iterator it; for( it = m_globalFunctions.begin(); it != m_globalFunctions.end(); ++it ) { if((*it).text.lower() == function.lower()){ KTextEditor::CompletionEntry e = (*it); list.append(e.text + e.postfix); } } FunctionList methodList = m_model->globalNamespace()->functionList(); FunctionList::Iterator methodIt; for (methodIt = methodList.begin(); methodIt != methodList.end(); ++methodIt) { if ((*methodIt)->name().lower() == function.lower()){ KTextEditor::CompletionEntry e; ArgumentDom pArgs; QString args = "()"; ArgumentDom pArg = (*methodIt)->argumentList().first(); if (pArgs) args = "(" + pArg->type() +")"; list.append((*methodIt)->name() + "(" + args +")"); } } return list; } ClassList classList = getClassByName(classname); ClassList::Iterator classIt; for (classIt = classList.begin(); classIt != classList.end(); ++classIt) { ClassDom nClass = *classIt; FunctionList methodList = nClass->functionList(); FunctionList::Iterator methodIt; for (methodIt = methodList.begin(); methodIt != methodList.end(); ++methodIt) { if ((*methodIt)->name().lower() == function.lower()) { ArgumentDom pArg = (*methodIt)->argumentList().first(); if (pArg) list.append(nClass->name() + "::" + function + "(" + pArg->type() +")"); } } if (nClass->baseClassList().count() != 0) { QStringList base = nClass->baseClassList(); QStringList::Iterator nameIt; for (nameIt = base.begin(); nameIt != base.end(); ++nameIt) { ClassList baseList = getClassByName(*nameIt); ClassList::Iterator baseIt; for (baseIt = baseList.begin(); baseIt != baseList.end(); ++baseIt) classList.append(*baseIt); } } } return list; } QString PHPCodeCompletion::getCurrentClassName() { kdDebug(9018) << "getCurrentClassName" << endl; QRegExp Class("^[ \t]*(abstract|final|)[ \t]*class[ \t]+([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[ \t]*(extends[ \t]*([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))?.*$"); Class.setCaseSensitive(FALSE); for(int i = m_currentLine; i >= 0; i--){ QString line = m_editInterface->textLine(i); if (!line.isNull()) { if (Class.search(line) != -1) return Class.cap(2); } } return QString::null; } QString PHPCodeCompletion::getClassName(QString varName, QString classname) { kdDebug(9018) << "getClassName " << varName << "::" << classname << endl; if (varName.find("$") == 0) varName = varName.mid(1); if (varName.lower() == "this") return this->getCurrentClassName(); if (classname.isEmpty()) { VariableList attrList = m_model->globalNamespace()->variableList(); VariableList::Iterator attrIt; for (attrIt = attrList.begin(); attrIt != attrList.end(); ++attrIt) { if ((*attrIt)->name().lower() == varName.lower()) return (*attrIt)->type(); } } ClassList classList = getClassByName( classname ); ClassList::Iterator classIt; for (classIt = classList.begin(); classIt != classList.end(); ++classIt) { ClassDom pClass = *classIt; FunctionList funcList = pClass->functionList(); FunctionList::Iterator funcIt; for (funcIt = funcList.begin(); funcIt != funcList.end(); ++funcIt) { if (QString((*funcIt)->name().lower() + "(") == varName.lower()) return (*funcIt)->resultType(); } VariableList attrList = pClass->variableList(); VariableList::Iterator attrIt; for (attrIt = attrList.begin(); attrIt != attrList.end(); ++attrIt) { if ((*attrIt)->name().lower() == varName.lower()) return (*attrIt)->type(); } } kdDebug(9018) << "Need " << classname << " " << varName << endl; /* /// @fixme peut devenir recursif voir xbutton.php ligne 204 QRegExp createmember("\\" + varName + "[ \t]*=[ \t]*(.*)[ \t]*;"); for(int i = m_currentLine; i >= 0; i--){ QString line = m_editInterface->textLine(i); if (!line.isNull() && line.find(varName,0 , FALSE) != -1) { if (createmember.search(line) != -1) { QString right = createmember.cap(1).stripWhiteSpace(); QStringList vars = QStringList::split("->", right); for ( QStringList::Iterator it = vars.begin(); it != vars.end(); ++it ) { QString objet = *it; ++it; if (it == vars.end()) break; QString var = *it; if (objet.lower() == "$this") objet = this->getCurrentClassName(); classname = getClassName(var, objet); NamespaceDom varns = m_model->globalNamespace()->namespaceByName("varsns"); QString fromns; if (varns) { QString name = objet + "::" + var; kdDebug(9018) << name << endl; VariableDom nVar = varns->variableByName(name); fromns = nVar->type(); } kdDebug(9018) << "Need " << objet << " " << var << " " << fromns << " " << vars.size() << endl; } kdDebug(9018) << "Dehors" << " " << classname << endl; return classname; } } } */ return ""; } QValueList PHPCodeCompletion::getClassByName(QString classname) { QValueList CList; ClassList classList = m_model->globalNamespace()->classList(); ClassList::Iterator classIt; for (classIt = classList.begin(); classIt != classList.end(); ++classIt) { ClassDom nClass = *classIt; if (nClass->name().lower() == classname.lower()) CList.append( nClass ); } return CList; } bool PHPCodeCompletion::checkForArgHint(QString line, int col) { kdDebug(9018) << "checkForArgHint" << endl; QValueList list; QStringList argsList; if (m_argWidgetShow == true) return false; if (line.find("::") != -1) { QRegExp Static("([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)::([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)"); Static.setCaseSensitive(FALSE); if (Static.search(line) != -1) { QString classname = Static.cap(1); QString function = Static.cap(2); argsList = getArguments(classname, function); if (argsList.count() > 0) { m_argWidgetShow = true; m_codeInterface->showArgHint ( argsList, "()", "," ); return true; } } } if (line.findRev("->") != -1) { int pos1 = line.findRev("->"); QString classname; QString function = line.mid(pos1 + 2); line = line.mid(0, pos1); kdDebug(9018) << "checkForArgHint 2 " << line << endl; QStringList vars = QStringList::split("->", line); for ( QStringList::Iterator it = vars.begin(); it != vars.end(); ++it ) { kdDebug(9018) << "for " << line << endl; classname = getClassName(*it, classname); kdDebug(9018) << "next " << line << endl; } kdDebug(9018) << "checkForArgHint 4 " << line << endl; argsList = getArguments(classname, function); if (argsList.count() > 0) { m_argWidgetShow = true; m_codeInterface->showArgHint ( argsList, "()", "," ); return true; } } kdDebug(9018) << "checkForArgHint 0 " << line << endl; argsList = getArguments("", line); if (argsList.count() > 0) { m_argWidgetShow = true; m_codeInterface->showArgHint ( argsList, "()", "," ); return true; } argsList = getArguments(line, line); if (argsList.count() > 0) { m_argWidgetShow = true; m_codeInterface->showArgHint ( argsList, "()", "," ); return true; } return false; } void PHPCodeCompletion::setStatusBar(QString expr, QString type) { m_phpSupport->mainWindow()->statusBar()->message( i18n("Type of %1 is %2").arg(expr).arg(type), 1000 ); } #include "phpcodecompletion.moc"