You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdevelop/languages/php/phpcodecompletion.cpp

713 lines
23 KiB

/*
Copyright (C) 2005 by Nicolas Escuder <n.escuder@intra-links.com>
Copyright (C) 2001 by smeier@tdevelop.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 <kdevcore.h>
#include <kinstance.h>
#include <kstandarddirs.h>
#include <kdebug.h>
#include <tqfile.h>
#include <tqtextstream.h>
#include <tqregexp.h>
#include <iostream>
#include "phpfile.h"
using namespace std;
PHPCodeCompletion::PHPCodeCompletion(PHPSupportPart *phpSupport, PHPConfigData *config) : TQObject(), 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();
TQString phpFuncFile = dirs->findResource("data","kdevphpsupport/phpfunctions");
TQRegExp lineReg(":([0-9A-Za-z_]+) ([0-9A-Za-z_]+)\\((.*)\\)");
FunctionCompletionEntry e;
TQFile f(phpFuncFile);
if ( f.open(IO_ReadOnly) ) { // file opened successfully
TQTextStream t( &f ); // use a text stream
TQString 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 = "(" + TQString(lineReg.cap(3)) + ")";
e.prototype = TQString(lineReg.cap(1)) + " " + TQString(lineReg.cap(2)) + "(" + TQString(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<KTextEditor::EditInterface*>(part);
if (!m_editInterface) {
kdDebug(9018) << "editor doesn't support the EditDocumentIface" << endl;
return;
}
m_cursorInterface = dynamic_cast<KTextEditor::ViewCursorInterface*>(part->widget());
if (!m_cursorInterface) {
kdDebug(9018) << "editor does not support the ViewCursorInterface" << endl;
return;
}
m_codeInterface = dynamic_cast<KTextEditor::CodeCompletionInterface*>(part->widget());
if (!m_codeInterface) { // no CodeCompletionDocument available
kdDebug(9018) << "editor doesn't support the CodeCompletionDocumentIface" << endl;
return;
}
m_selectionInterface = dynamic_cast<KTextEditor::SelectionInterface*>(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(), TQT_SIGNAL(cursorPositionChanged()), this, TQT_SLOT(cursorPositionChanged()));
connect( part, TQT_SIGNAL(textChanged()), this, TQT_SLOT(cursorPositionChanged()) );
connect(part->widget(), TQT_SIGNAL(argHintHidden()), this, TQT_SLOT(argHintHided()));
connect(part->widget(), TQT_SIGNAL(completionAborted()), this, TQT_SLOT(completionBoxHided()));
connect(part->widget(), TQT_SIGNAL(completionDone()), this, TQT_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;
TQString 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(TQRegExp("[ \\t=;\\$\\.\\(\\)]"), pos1 - 1);
int pos3 = lineStr.findRev(")", col);
if (pos1 > pos2 && pos1 != -1 && pos3 < pos1) {
TQString 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(TQRegExp("[ \\t=;\\$\\.\\(\\)]"), col - 1);
TQString 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(TQRegExp("[ \\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(TQValueList<KTextEditor::CompletionEntry> 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(TQString line, int col) {
kdDebug(9018) << "checkForStaticFunction" << endl;
TQValueList<KTextEditor::CompletionEntry> list;
if (line.find("::") == -1)
return false;
TQRegExp 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) {
TQString classname = Class.cap(1);
TQString 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().tqstartsWith(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) {
TQStringList base = nClass->baseClassList();
TQStringList::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(TQString line, int col){
kdDebug(9018) << "checkForNew" << endl;
TQValueList<KTextEditor::CompletionEntry> list;
if (line.find("new ", 0, FALSE) == -1)
return false;
TQRegExp 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(TQString line, int col){
kdDebug(9018) << "checkForExtends" << endl;
TQValueList<KTextEditor::CompletionEntry> list;
if (line.find("extends", 0, FALSE) == -1)
return false;
TQRegExp 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(TQString line, int col){
kdDebug(9018) << "checkForVariable" << endl;
TQValueList<KTextEditor::CompletionEntry> list;
TQString 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);
}
TQStringList vars = TQStringList::split("->", line);
TQString classname;
for ( TQStringList::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(TQString line, int col) {
kdDebug(9018) << "checkForGlobalFunction(" + line + "," << col << endl;
TQValueList<KTextEditor::CompletionEntry> list;
if (line.length() < 3)
return false;
list = this->getFunctionsAndVars("", line);
return showCompletionBox(list, line.length());
}
TQValueList<KTextEditor::CompletionEntry> PHPCodeCompletion::getClasses(TQString name) {
TQValueList<KTextEditor::CompletionEntry> list;
TQStringList 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().tqstartsWith(name, FALSE)) {
KTextEditor::CompletionEntry e;
TQStringList::Iterator it = added.find(nClass->name());
if (it == added.end()) {
e.text = nClass->name();
list.append(e);
added.append(nClass->name());
}
}
}
return list;
}
TQValueList<KTextEditor::CompletionEntry> PHPCodeCompletion::getFunctionsAndVars(TQString classname, TQString function) {
kdDebug(9018) << "getFunctionsAndVars " << classname << endl;
TQValueList<KTextEditor::CompletionEntry> list;
if (classname.isEmpty()) {
TQValueList<FunctionCompletionEntry>::Iterator it;
for( it = m_globalFunctions.begin(); it != m_globalFunctions.end(); ++it ) {
if((*it).text.tqstartsWith(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().tqstartsWith(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().tqstartsWith(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().tqstartsWith(function, FALSE)) {
KTextEditor::CompletionEntry e;
e.prefix = nClass->name() + " ::";
e.text = pVar->name();
e.postfix = "";
list.append(e);
}
}
if (nClass->baseClassList().count() != 0) {
TQStringList base = nClass->baseClassList();
TQStringList::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;
}
TQStringList PHPCodeCompletion::getArguments(TQString classname, TQString function) {
kdDebug(9018) << "getArguments " << function << endl;
TQStringList list;
if (classname.isEmpty()) {
TQValueList<FunctionCompletionEntry>::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;
TQString 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) {
TQStringList base = nClass->baseClassList();
TQStringList::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;
}
TQString PHPCodeCompletion::getCurrentClassName() {
kdDebug(9018) << "getCurrentClassName" << endl;
TQRegExp 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--){
TQString line = m_editInterface->textLine(i);
if (!line.isNull()) {
if (Class.search(line) != -1)
return Class.cap(2);
}
}
return TQString();
}
TQString PHPCodeCompletion::getClassName(TQString varName, TQString 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 (TQString((*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
TQRegExp createmember("\\" + varName + "[ \t]*=[ \t]*(.*)[ \t]*;");
for(int i = m_currentLine; i >= 0; i--){
TQString line = m_editInterface->textLine(i);
if (!line.isNull() && line.find(varName,0 , FALSE) != -1) {
if (createmember.search(line) != -1) {
TQString right = createmember.cap(1).stripWhiteSpace();
TQStringList vars = TQStringList::split("->", right);
for ( TQStringList::Iterator it = vars.begin(); it != vars.end(); ++it ) {
TQString objet = *it;
++it;
if (it == vars.end())
break;
TQString var = *it;
if (objet.lower() == "$this")
objet = this->getCurrentClassName();
classname = getClassName(var, objet);
NamespaceDom varns = m_model->globalNamespace()->namespaceByName("varsns");
TQString fromns;
if (varns) {
TQString 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 "";
}
TQValueList<ClassDom> PHPCodeCompletion::getClassByName(TQString classname) {
TQValueList<ClassDom> 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(TQString line, int col) {
kdDebug(9018) << "checkForArgHint" << endl;
TQValueList<KTextEditor::CompletionEntry> list;
TQStringList argsList;
if (m_argWidgetShow == true)
return false;
if (line.find("::") != -1) {
TQRegExp 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) {
TQString classname = Static.cap(1);
TQString 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("->");
TQString classname;
TQString function = line.mid(pos1 + 2);
line = line.mid(0, pos1);
kdDebug(9018) << "checkForArgHint 2 " << line << endl;
TQStringList vars = TQStringList::split("->", line);
for ( TQStringList::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(TQString expr, TQString type) {
m_phpSupport->mainWindow()->statusBar()->message( i18n("Type of %1 is %2").tqarg(expr).tqarg(type), 1000 );
}
#include "phpcodecompletion.moc"