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.
kile/src/kile/codecompletion.cpp

1476 lines
40 KiB

/***************************************************************************
date : Mar 21 2007
version : 0.40
copyright : (C) 2004-2007 by Holger Danielsson
email : holger.danielsson@versanet.de
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "codecompletion.h"
#include <tqregexp.h>
#include <tqfile.h>
#include <tqtimer.h>
#include <tqdict.h>
#include "kiledebug.h"
#include <tdelocale.h>
#include <tdeglobal.h>
#include <kstandarddirs.h>
#include <tdeconfig.h>
#include <tdemessagebox.h>
#include <tdetexteditor/codecompletioninterface.h>
#include "kileinfo.h"
#include "kileviewmanager.h"
#include "kileconfig.h"
#include "kileedit.h"
#include "kiledebug.h"
namespace KileDocument
{
static TQRegExp reRef;
static TQRegExp reRefExt;
static TQRegExp reCite;
static TQRegExp reCiteExt;
static TQRegExp reNotRefChars("[^a-zA-Z0-9_@\\.\\+\\-\\*\\:]");
static TQRegExp reNotCiteChars("[^a-zA-Z0-9_@\\-\\:]");
CodeCompletion::CodeCompletion(KileInfo *info) : m_ki(info), m_view(0L)
{
m_firstconfig = true;
m_inprogress = false;
m_undo = false;
m_ref = false;
m_kilecompletion = false;
m_keylistType = CodeCompletion::ctNone;
// local abbreviation list
m_abbrevListview = 0L;
m_localAbbrevFile = locateLocal("appdata", "complete/abbreviation/", true) + "kile-abbrevs.cwl";
//reRef.setPattern("^\\\\(pageref|ref|xyz)\\{");
m_completeTimer = new TQTimer( this );
connect(m_completeTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotCompleteValueList() ) );
}
CodeCompletion::~CodeCompletion() {}
bool CodeCompletion::isActive()
{
return m_isenabled;
}
bool CodeCompletion::inProgress()
{
return m_inprogress;
}
bool CodeCompletion::autoComplete()
{
return m_autocomplete || m_autocompletetext;
}
CodeCompletion::Type CodeCompletion::getType()
{
return m_type;
}
CodeCompletion::Type CodeCompletion::getType( const TQString &text )
{
if ( text.find( reRef ) != -1 )
return CodeCompletion::ctReference;
else if ( text.find( reCite ) != -1 )
return CodeCompletion::ctCitation;
else
return CodeCompletion::ctNone;
}
CodeCompletion::Mode CodeCompletion::getMode()
{
return m_mode;
}
CodeCompletion::Type CodeCompletion::insideReference(TQString &startpattern)
{
if ( m_view->getDoc() )
{
uint column = m_view->cursorColumnReal();
TQString currentline = m_view->getDoc()->textLine(m_view->cursorLine()).left(column);
int pos = currentline.findRev('\\');
if ( pos >= 0 )
{
TQString command = currentline.mid(pos,column-pos);
KILE_DEBUG() << "pos=" << pos << ",column=" << column << ",currentline=" << currentline << ",command=" << command << endl;
if( command.find(reRefExt) != -1 ){
KILE_DEBUG() << "reRefExt" << endl;
startpattern = command.right(command.length()-reRefExt.cap(0).length());
KILE_DEBUG() << "startpattern=" << startpattern << endl;
if ( startpattern.find(reNotRefChars) == -1 ){
return CodeCompletion::ctReference ;
}
}
else if ( command.find(reRef) != -1 ){
startpattern = command.right(command.length()-reRef.cap(0).length());
KILE_DEBUG() << "startpattern=" << startpattern << endl;
if ( startpattern.find(reNotRefChars) == -1 ){
return CodeCompletion::ctReference ;
}
}
else if( command.find(reCiteExt) != -1 ){
KILE_DEBUG() << "reCiteExt" << endl;
startpattern = command.right(command.length()-reCiteExt.cap(0).length());
KILE_DEBUG() << "startpattern=" << startpattern << endl;
if ( startpattern.find(reNotCiteChars) == -1 ){
return CodeCompletion::ctCitation;
}
}
else if ( command.find(reCite) != -1 ){
startpattern = command.right(command.length()-reCite.cap(0).length());
KILE_DEBUG() << "startpattern=" << startpattern << endl;
if ( startpattern.find(reNotCiteChars) == -1 ){
return CodeCompletion::ctCitation;
}
}
}
}
return CodeCompletion::ctNone;
}
//////////////////// configuration ////////////////////
void CodeCompletion::readConfig(TDEConfig *config)
{
KILE_DEBUG() << "=== CodeCompletion::readConfig ===================" << endl;
// save normal parameter
//KILE_DEBUG() << " read bool entries" << endl;
m_isenabled = KileConfig::completeEnabled();
m_setcursor = KileConfig::completeCursor();
m_setbullets = KileConfig::completeBullets();
m_closeenv = KileConfig::completeCloseEnv();
m_autocomplete = KileConfig::completeAuto();
m_autocompletetext = KileConfig::completeAutoText();
m_autocompleteabbrev = KileConfig::completeAutoAbbrev();
m_latexthreshold = KileConfig::completeAutoThreshold();
m_textthreshold = KileConfig::completeAutoTextThreshold();
m_citationMove = KileConfig::completeCitationMove();
m_autoDollar = KileConfig::autoInsertDollar();
// we need to read some of Kate's config flags
readKateConfigFlags(config);
// reading the wordlists is only necessary at the first start
// and when the list of files changes
if ( m_firstconfig || KileConfig::completeChangedLists() || KileConfig::completeChangedCommands() )
{
KILE_DEBUG() << " set regexp for references..." << endl;
setReferences();
KILE_DEBUG() << " read wordlists..." << endl;
// wordlists for Tex/Latex mode
TQStringList files = KileConfig::completeTex();
setWordlist( files, "tex", &m_texlist );
// wordlist for dictionary mode
files = KileConfig::completeDict();
setWordlist( files, "dictionary", &m_dictlist );
// wordlist for abbreviation mode
files = KileConfig::completeAbbrev();
setWordlist( files, "abbreviation", &m_abbrevlist );
// remember changed lists
m_firstconfig = false;
KileConfig::setCompleteChangedLists(false);
KileConfig::setCompleteChangedCommands(false);
}
}
void CodeCompletion::readKateConfigFlags(TDEConfig *config)
{
config->setGroup("Kate Document Defaults");
m_autobrackets = ( config->readNumEntry("Basic Config Flags",0) & cfAutoBrackets );
m_autoindent = ( config->readNumEntry("Indentation Mode",0) > 0 );
}
// save local abbreviation changes
// (for example before a new configuration should be read)
void CodeCompletion::saveLocalChanges()
{
KILE_DEBUG() << "=== CodeCompletion::saveLocalChanges ===================" << endl;
m_abbrevListview->saveLocalAbbreviation(m_localAbbrevFile);
}
//////////////////// references and citations ////////////////////
void CodeCompletion::setReferences()
{
// build list of references
TQString references = getCommandList(KileDocument::CmdAttrReference);
references.replace("*","\\*");
reRef.setPattern("^\\\\(" + references + ")\\{");
reRefExt.setPattern("^\\\\(" + references + ")\\{[^\\{\\}\\\\]+,");
// build list of citations
TQString citations = getCommandList(KileDocument::CmdAttrCitations);
citations.replace("*","\\*");
reCite.setPattern("^\\\\(((c|C|noc)(ite|itep|itet|itealt|itealp|iteauthor|iteyear|iteyearpar|itetext))" + citations + ")\\{");
reCiteExt.setPattern("^\\\\(((c|C|noc)(ite|itep|itet|itealt|itealp|iteauthor|iteyear|iteyearpar|itetext))" + citations + ")\\{[^\\{\\}\\\\]+,");
}
TQString CodeCompletion::getCommandList(KileDocument::CmdAttribute attrtype)
{
TQStringList cmdlist;
TQStringList::ConstIterator it;
// get info about user defined references
KileDocument::LatexCommands *cmd = m_ki->latexCommands();
cmd->commandList(cmdlist,attrtype,false);
// build list of references
TQString commands = TQString();
for ( it=cmdlist.begin(); it != cmdlist.end(); ++it )
{
if ( cmd->isStarredEnv(*it) )
commands += '|' + (*it).mid(1) + '*';
commands += '|' + (*it).mid(1);
}
return commands;
}
//////////////////// wordlists ////////////////////
void CodeCompletion::setWordlist( const TQStringList &files, const TQString &dir,
TQValueList<KTextEditor::CompletionEntry> *entrylist
)
{
// read wordlists from files
TQStringList wordlist;
for ( uint i = 0; i < files.count(); ++i )
{
// if checked, the wordlist has to be read
if ( files[ i ].at( 0 ) == '1' )
{
readWordlist( wordlist, dir + '/' + files[i].right( files[i].length()-2 ) + ".cwl", true );
}
}
// add user defined commands and environments
if ( dir == "tex" )
{
addCommandsToTexlist(wordlist);
setCompletionEntriesTexmode( entrylist, wordlist );
}
else if ( dir == "dictionary" )
{
wordlist.sort();
setCompletionEntries( entrylist, wordlist );
}
else if ( dir == "abbreviation" )
{
// read local wordlist
TQStringList localWordlist;
readWordlist(localWordlist, TQString(), false);
// add local/global wordlists to the abbreviation view
m_abbrevListview->init(&wordlist,&localWordlist);
// finally add local wordlists to the abbreviation list
TQStringList::ConstIterator it;
for ( it=localWordlist.begin(); it!=localWordlist.end(); ++it )
wordlist.append( *it );
wordlist.sort();
setCompletionEntries( entrylist, wordlist );
}
}
void CodeCompletion::addCommandsToTexlist(TQStringList &wordlist)
{
TQStringList cmdlist;
TQStringList::ConstIterator it;
KileDocument::LatexCmdAttributes attr;
// get info about user defined commands and environments
KileDocument::LatexCommands *cmd = m_ki->latexCommands();
cmd->commandList(cmdlist,KileDocument::CmdAttrNone,true);
// add entries to wordlist
for ( it=cmdlist.begin(); it != cmdlist.end(); ++it )
{
if ( cmd->commandAttributes(*it,attr) )
{
TQString command,eos;
TQStringList entrylist;
if ( attr.type < KileDocument::CmdAttrLabel ) // environment
{
command = "\\begin{" + (*it);
eos = "}";
}
else // command
{
command = (*it);
// eos = TQString();
}
// get all possibilities into a stringlist
entrylist.append( command + eos );
if ( ! attr.option.isEmpty() )
entrylist.append( command + eos + "[option]" );
if ( attr.starred )
{
entrylist.append( command + '*' + eos );
if ( ! attr.option.isEmpty() )
entrylist.append( command + '*' + eos + "[option]" );
}
// finally append entries to wordlist
TQStringList::ConstIterator itentry;
for ( itentry=entrylist.begin(); itentry != entrylist.end(); ++itentry )
{
TQString entry = (*itentry);
if ( ! attr.parameter.isEmpty() )
entry += "{param}";
if ( attr.type == KileDocument::CmdAttrList )
entry += "\\item";
wordlist.append( entry );
}
}
}
}
//////////////////// completion box ////////////////////
void CodeCompletion::completeWord(const TQString &text, CodeCompletion::Mode mode)
{
KILE_DEBUG() << "==CodeCompletion::completeWord(" << text << ")=========" << endl;
KILE_DEBUG() << "\tm_view = " << m_view << endl;
if ( !m_view) return;
KILE_DEBUG() << "ok" << endl;
// remember all parameters (view, pattern, length of pattern, mode)
m_text = text;
m_textlen = text.length();
m_mode = mode;
// and the current cursor position
m_view->cursorPositionReal( &m_ycursor, &m_xcursor );
m_xstart = m_xcursor - m_textlen;
// and the current document
Kate::Document *doc = m_view->getDoc();
// switch to cmLatex mode, if cmLabel is chosen without any entries
if ( mode==cmLabel && m_labellist.count()==0 ) {
TQString s = doc->textLine(m_ycursor);
int pos = s.findRev("\\",m_xcursor);
if (pos < 0) {
KILE_DEBUG() << "\tfound no backslash! s=" << s << endl;
return;
}
m_xstart = pos;
m_text = doc->text(m_ycursor,m_xstart,m_ycursor,m_xcursor);
m_textlen = m_text.length();
m_mode = cmLatex;
}
// determine the current list
TQValueList<KTextEditor::CompletionEntry> list;
switch ( m_mode )
{
case cmLatex: // fall through here
case cmEnvironment:
list = m_texlist;
appendNewCommands(list);
break;
case cmDictionary:
list = m_dictlist;
break;
case cmAbbreviation:
list = m_abbrevlist;
break;
case cmLabel:
list = m_labellist;
break;
case cmDocumentWord:
getDocumentWords(text,list);
break;
}
// is it necessary to show the complete dialog?
TQString entry, type;
TQString pattern = ( m_mode != cmEnvironment ) ? text : "\\begin{" + text;
uint n = countEntries( pattern, &list, &entry, &type );
KILE_DEBUG() << "entries = " << n << endl;
// nothing to do
if ( n == 0 )
return ;
// Add a prefix ('\\begin{', length=7) in cmEnvironment mode,
// because KateCompletion reads from the current line, This also
// means that the original text has to be restored, if the user
// aborts the completion dialog
if ( m_mode == cmEnvironment )
{
doc->removeText( m_ycursor, m_xstart, m_ycursor, m_xcursor );
doc->insertText( m_ycursor, m_xstart, "\\begin{" + m_text );
// set the cursor to the new position
m_textlen += 7;
m_xcursor += 7;
m_view->setCursorPositionReal( m_ycursor, m_xcursor );
// set restore mode
m_undo = true;
}
// set restore mode
if ( m_mode == cmAbbreviation ) m_undo = true;
// show the completion dialog
m_inprogress = true;
KTextEditor::CodeCompletionInterface *iface;
iface = dynamic_cast<KTextEditor::CodeCompletionInterface *>( m_view );
iface->showCompletionBox( list, m_textlen );
}
void CodeCompletion::appendNewCommands(TQValueList<KTextEditor::CompletionEntry> & list)
{
KTextEditor::CompletionEntry e;
const TQStringList *ncommands = m_ki->allNewCommands();
TQStringList::ConstIterator it;
TQStringList::ConstIterator itend(ncommands->end());
for ( it = ncommands->begin(); it != itend; ++it )
{
e.text = *it;
list.prepend(e);
}
}
void CodeCompletion::completeFromList(const TQStringList *list,const TQString &pattern)
{
KTextEditor::CompletionEntry e;
KILE_DEBUG() << "completeFromList: " << list->count() << " items" << " << pattern=" << pattern<< endl;
TQStringList sortedlist( *list );
sortedlist.sort();
m_labellist.clear();
TQStringList::ConstIterator it;
TQStringList::ConstIterator itend(sortedlist.end());
for ( it = sortedlist.begin(); it != itend; ++it )
{
e.text = *it;
m_labellist.append( e );
}
completeWord(pattern, cmLabel);
}
//////////////////// completion was done ////////////////////
void CodeCompletion::CompletionDone(KTextEditor::CompletionEntry)
{
// kile completion: is there a new cursor position?
if ( m_kilecompletion && m_setcursor && ( m_xoffset != 0 || m_yoffset != 0 ) && m_view )
{
int newx = ( m_xoffset != 0 ) ? m_xcursor + m_xoffset - m_textlen : m_xcursor;
int newy = ( m_yoffset != 0 ) ? m_ycursor + m_yoffset : m_ycursor;
m_view->setCursorPositionReal( newy, newx );
}
m_undo = false;
m_inprogress = false;
m_ref = false;
}
void CodeCompletion::CompletionAborted()
{
KILE_DEBUG() << "CodeCompletion::CompletionAborted()" << endl;
// aborted: undo if kile completion is active
if ( m_inprogress && m_undo && m_view )
{
uint row, col;
m_view->cursorPositionReal( &row, &col );
Kate::Document *doc = m_view->getDoc();
doc->removeText( m_ycursor, m_xstart, m_ycursor, col );
doc->insertText( m_ycursor, m_xstart, m_text );
m_view->setCursorPositionReal( m_ycursor, m_xstart + m_text.length() );
}
m_undo = false;
m_inprogress = false;
m_ref = false;
}
//////////////////// build the text for completion ////////////////////
// parse an entry for kile completion modes:
// - delete arguments/parameters
// - set cursor position
// - insert bullets
TQString CodeCompletion::filterCompletionText( const TQString &text, const TQString &type )
{
static TQRegExp reEnv = TQRegExp("^\\\\(begin|end)[^a-zA-Z]+");
KILE_DEBUG() << " complete filter: " << text << " type " << type << endl;
m_type = getType( text ); // remember current type
if ( text!="\\begin{}" && reEnv.search(text)!=-1 )
m_mode = cmEnvironment;
// check the cursor position, because the user may have
// typed some characters or the backspace key. This also
// changes the length of the current pattern.
uint row, col;
m_view->cursorPositionReal( &row, &col );
if ( m_xcursor != col )
{
m_textlen += ( col - m_xcursor );
m_xcursor = col;
}
// initialize offset for the new cursorposition
m_xoffset = m_yoffset = 0;
// build the text
TQString s,prefix;
Kate::Document *doc = m_view->getDoc();
TQString textline = doc->textLine(row);
switch ( m_mode )
{
case cmLatex:
s = buildLatexText( text, m_yoffset, m_xoffset );
if ( m_autobrackets && textline.at(col)=='}' && m_text.find('{')>=0 )
{
doc->removeText(row,col,row,col+1);
}
break;
case cmEnvironment:
prefix = TQString();
if ( m_autoindent )
{
if ( col-m_textlen>0 )
{
prefix = textline.left(col-m_textlen);
if ( prefix.right(7) == "\\begin{" )
prefix.truncate(prefix.length()-7);
else if ( prefix.right(5) == "\\end{" )
prefix.truncate(prefix.length()-5);
}
}
s = buildEnvironmentText( text, type, prefix, m_yoffset, m_xoffset );
if ( m_autobrackets && textline.at(col)=='}' && (textline[m_xstart]!='\\' || m_text.find('{')>=0 ) )
{
doc->removeText(row,col,row,col+1);
}
if ( m_xstart>=7 && textline.mid(m_xstart-7,7) == "\\begin{" )
{
m_textlen += 7;
}
else if ( m_xstart>=5 && textline.mid(m_xstart-5,5) == "\\end{" )
{
m_textlen += 5;
}
break;
case cmDictionary:
s = text;
break;
case cmAbbreviation:
s = buildAbbreviationText( text );
break;
case cmLabel:
s = buildLabelText( text );
if ( m_keylistType==CodeCompletion::ctReference
|| (m_keylistType==CodeCompletion::ctCitation && m_citationMove) )
{
m_xoffset = s.length() + 1;
}
break;
case cmDocumentWord:
s = text;
break;
default : s = text; break;
}
if ( s.length() > m_textlen )
return s.right( s.length() - m_textlen );
else
return "";
}
//////////////////// text in cmLatex mode ////////////////////
TQString CodeCompletion::buildLatexText( const TQString &text, uint &ypos, uint &xpos )
{
return parseText( stripParameter( text ), ypos, xpos, true );
}
//////////////////// text in cmEnvironment mode ////////////////////
TQString CodeCompletion::buildEnvironmentText( const TQString &text, const TQString &type,
const TQString &prefix, uint &ypos, uint &xpos )
{
static TQRegExp reEnv = TQRegExp("^\\\\(begin|end)\\{([^\\}]*)\\}(.*)");
if (reEnv.search(text) == -1) return text;
TQString parameter = stripParameter( reEnv.cap(3) );
TQString start = reEnv.cap(1);
TQString envname = reEnv.cap(2);
TQString whitespace = getWhiteSpace(prefix);
TQString envIndent = m_ki->editorExtension()->autoIndentEnvironment();
TQString s = "\\" + start + "{" + envname + "}" + parameter + "\n";
s += whitespace;
if ( start != "end" )
s += envIndent;
bool item = (type == "list" );
if ( item )
s += "\\item ";
if ( m_setbullets && !parameter.isEmpty() )
s += s_bullet;
if ( m_closeenv && start != "end" )
s += '\n' + whitespace + "\\end{" + envname + "}\n";
// place cursor
if ( m_setcursor )
{
if ( parameter.isEmpty() )
{
ypos = 1;
xpos = whitespace.length() + envIndent.length() + (( item ) ? 6 : 0);
}
else
{
ypos = 0;
if( parameter.left(2) == "[<" )
xpos = 10 + envname.length();
else
xpos = 9 + envname.length();
}
}
return s;
}
TQString CodeCompletion::getWhiteSpace(const TQString &s)
{
TQString whitespace = s;
for ( uint i=0; i<whitespace.length(); ++i )
{
if ( ! whitespace[i].isSpace() )
whitespace[i] = ' ';
}
return whitespace;
}
//////////////////// text in cmAbbreviation mode ////////////////////
TQString CodeCompletion::buildAbbreviationText( const TQString &text )
{
TQString s;
int index = text.find( '=' );
if ( index >= 0 )
{
// determine text to insert
s = text.right( text.length() - index - 1 );
// delete abbreviation
Kate::Document *doc = m_view->getDoc();
doc->removeText( m_ycursor, m_xstart, m_ycursor, m_xcursor );
m_view->setCursorPositionReal( m_ycursor, m_xstart );
m_xcursor = m_xstart;
m_textlen = 0;
}
else
s = "";
return s;
}
//////////////////// text in cmLabel mode ////////////////////
TQString CodeCompletion::buildLabelText( const TQString &text )
{
if ( text.at( 0 ) == ' ' )
{
// delete space
Kate::Document * doc = m_view->getDoc();
doc->removeText( m_ycursor, m_xstart, m_ycursor, m_xstart + 1 );
m_view->setCursorPositionReal( m_ycursor, m_xstart );
m_xcursor = m_xstart;
m_textlen = 0;
return text.right( text.length() - 1 );
}
else
return text;
}
//////////////////// some functions ////////////////////
TQString CodeCompletion::parseText( const TQString &text, uint &ypos, uint &xpos, bool checkgroup )
{
bool foundgroup = false;
TQString s = "";
xpos = ypos = 0;
for ( uint i = 0; i < text.length(); ++i )
{
switch ( text[ i ] )
{
case '<':
case '{':
case '(':
case '[': // insert character
s += text[ i ];
if ( xpos == 0 )
{
// remember position after first brace
if(text[i] == '[' && (i+1) < text.length() && text[i+1] == '<'){
xpos = i + 2;
s += text[i+1];
i++;
}// special handling for '[<'
else
xpos = i + 1;
// insert bullet, if this is no cursorposition
if ( ( ! m_setcursor ) && m_setbullets && !( text[i] == '[' && (i+1) < text.length() && text[i+1] == '<' ))
s += s_bullet;
}
// insert bullets after following braces
else if ( m_setbullets && !( text[i] == '[' && (i+1) < text.length() && text[i+1] == '<' ) )
s += s_bullet;
break;
case '>':
case '}':
case ')':
case ']': // insert character
s += text[ i ];
break;
case ',': // insert character
s += text[ i ];
// insert bullet?
if ( m_setbullets )
s += s_bullet;
break;
case '.': // if the last character is a point of a range operator,
// it will be replaced by a space or a bullet surrounded by spaces
if ( checkgroup && ( s.right( 1 ) == "." ) )
{
foundgroup = true;
s.truncate( s.length() - 1 );
if ( m_setbullets )
s += ' ' + s_bullet + ' ';
else
s += ' ';
}
else
s += text[ i ];
break;
default: // insert all other characters
s += text[ i ];
break;
}
}
// some more work with groups and bullets
if ( checkgroup && foundgroup && ( m_setbullets | m_setcursor ) )
{
int pos = 0;
// search for braces, brackets and parens
switch ( TQChar( s[ 1 ] ) )
{
case 'l' :
if ( s.left( 6 ) == "\\left " )
pos = 5;
break;
case 'b' :
if ( s.left( 6 ) == "\\bigl " )
pos = 5;
else if ( s.left( 7 ) == "\\biggl " )
pos = 6;
break;
case 'B' :
if ( s.left( 6 ) == "\\Bigl " )
pos = 5;
else if ( s.left( 7 ) == "\\Biggl " )
pos = 6;
break;
}
// update cursorposition and set bullet
if ( pos > 0 )
{
if ( m_setcursor )
xpos = pos;
if ( m_setbullets )
{
if ( ! m_setcursor )
s.insert( pos, s_bullet );
s.append( s_bullet );
}
}
}
// Ergebnis
return s;
}
// strip all names enclosed in braces
// consider also beamer like stuff [<...>] and <...>
TQString CodeCompletion::stripParameter( const TQString &text )
{
TQString s = "";
const TQChar *ch = text.unicode();
bool ignore = false;
for ( uint i = 0; i < text.length(); ++i )
{
switch ( *ch )
{
case '[':
case '{':
case '(':
case '<':
s += *ch;
ignore = true;
break;
case ']':
case '}':
case ')':
case '>':
s += *ch;
ignore = false;
break;
case ',':
s += *ch;
break;
default:
if ( ! ignore )
s += *ch;
break;
}
++ch;
}
return s;
}
//////////////////// read wordlists ////////////////////
void CodeCompletion::readWordlist( TQStringList &wordlist, const TQString &filename, bool global )
{
TQString file = ( global )
? TDEGlobal::dirs()->findResource( "appdata", "complete/" + filename )
: m_localAbbrevFile;
if ( file.isEmpty() ) return;
TQFile f( file );
if ( f.open( IO_ReadOnly ) )
{ // file opened successfully
TQTextStream t( &f ); // use a text stream
while ( ! t.eof() )
{ // until end of file...
TQString s = t.readLine().stripWhiteSpace(); // line of text excluding '\n'
if ( ! ( s.isEmpty() || s.at( 0 ) == '#' ) )
{
wordlist.append( s );
}
}
f.close();
}
}
void CodeCompletion::setCompletionEntries( TQValueList<KTextEditor::CompletionEntry> *list,
const TQStringList &wordlist )
{
// clear the list of completion entries
list->clear();
KTextEditor::CompletionEntry e;
TQStringList::ConstIterator it;
// build new entries
for ( it=wordlist.begin(); it != wordlist.end(); ++it )
{
// set CompletionEntry
e.text = *it;
e.type = "";
// add new entry
if ( list->findIndex(e) == -1 )
list->append(e);
}
}
void CodeCompletion::setCompletionEntriesTexmode( TQValueList<KTextEditor::CompletionEntry> *list,
const TQStringList &wordlist )
{
// clear the list of completion entries
list->clear();
// create a TQMap for a user defined sort
// order: \abc, \abc[], \abc{}, \abc*, \abc*[], \abc*{}, \abcd, \abcD
TQStringList keylist;
TQMap<TQString,TQString> map;
for ( uint i=0; i< wordlist.count(); ++i )
{
TQString s = wordlist[i];
for ( uint j=0; j<s.length(); ++j )
{
TQChar ch = s[j];
if ( ch.latin1()>='A' && ch.latin1()<='Z' )
s[j] = (int)ch + 32 ;
else if ( ch.latin1()>='a' && ch.latin1()<='z' )
s[j] = (int)ch - 32 ;
else if ( ch == '}' )
s[j] = 48;
else if ( ch == '{' )
s[j] = 49;
else if ( ch == '[' )
s[j] = 50;
else if ( ch == '*' )
s[j] = 51;
else if ( ch == ']' )
s[j] = 52;
}
// don't allow duplicate entries
if ( ! map.contains(s) )
{
map[s] = wordlist[i];
keylist.append(s);
}
}
// sort mapped keys
keylist.sort();
// build new entries: get the sorted keys and insert
// the real entries, which are saved in the map.
// if the last 5 chars of an environment are '\item', it is a
// list environment, where the '\item' tag is also inserted
KTextEditor::CompletionEntry e;
TQStringList::ConstIterator it;
for ( it=keylist.begin(); it != keylist.end(); ++it )
{
// get real entry
TQString s = map[*it];
if ( s.left( 7 ) == "\\begin{" && s.right( 5 ) == "\\item" )
{
e.text = s.left( s.length() - 5 ); // list environment entry
e.type = "list";
}
else
{
e.text = s; // normal entry
e.type = "";
}
// add new entry (duplicates are impossible)
list->append(e);
}
}
//////////////////// determine number of entries ////////////////////
// Count the number of entries. Stop, wenn there are 2 entries,
// because special functions are only called, when there are 0
// or 1 entries.
uint CodeCompletion::countEntries( const TQString &pattern,
TQValueList<KTextEditor::CompletionEntry> *list,
TQString *entry, TQString *type )
{
TQValueList<KTextEditor::CompletionEntry>::Iterator it;
uint n = 0;
for ( it = list->begin(); it != list->end() && n < 2; ++it )
{
if ( ( *it ).text.startsWith( pattern ) )
{
*entry = ( *it ).text;
*type = ( *it ).type;
++n;
}
}
return n;
}
TQString CodeCompletion::findExpansion(const TQString &abbrev)
{
TQValueList<KTextEditor::CompletionEntry>::Iterator it;
for ( it=m_abbrevlist.begin(); it!=m_abbrevlist.end(); ++it )
{
TQString s = (*it).text;
int index = s.find("=");
if ( index>=0 && s.left(index)==abbrev )
return s.right( s.length()-index-1 );
}
return TQString();
}
void CodeCompletion::editComplete(Kate::View *view, Mode mode)
{
KILE_DEBUG() << "void CodeCompletion::editComplete(Kate::View *view, Mode "<< mode << ")" << endl;
KILE_DEBUG() << "m_inprogress=" << m_inprogress << ", isActive()=" << isActive() << endl;
m_view = view;
if ( !m_view || !isActive() )
return ;
m_inprogress=true;
KILE_DEBUG() << "proceeded" << endl;
// check for a special case: call from inside of a reference command
if ( mode==cmLatex )
{
TQString startpattern;
CodeCompletion::Type reftype = insideReference(startpattern);
if ( reftype != CodeCompletion::ctNone )
{
m_ref = true;
editCompleteList(reftype,startpattern);
return;
}
}
TQString word;
Type type;
if ( getCompleteWord(( mode == cmLatex ) ? true : false, word, type ) )
{
if ( mode == cmLatex && word.at( 0 ) != '\\' )
{
mode = cmDictionary;
}
if ( type == CodeCompletion::ctNone )
completeWord(word, mode);
else
editCompleteList(type);
}
}
void CodeCompletion::editCompleteList(Type type,const TQString &pattern)
{
KILE_DEBUG() << "==editCompleteList=============" << endl;
m_keylistType = type;
if ( type == ctReference )
completeFromList(info()->allLabels(),pattern);
else if ( type == ctCitation )
completeFromList(info()->allBibItems(),pattern);
else
{
m_keylistType = CodeCompletion::ctNone;
kdWarning() << "unsupported type in CodeCompletion::editCompleteList" << endl;
}
}
//////////////////// slots for code completion ////////////////////
void CodeCompletion::slotCompletionDone(KTextEditor::CompletionEntry entry)
{
KILE_DEBUG() << "==slotCompletionDone (" << m_kilecompletion << "," << m_inprogress << ")=============" << endl;
CompletionDone(entry);
// if kile completion was active, look if we need to show an additional list
if ( m_kilecompletion )
{
m_kilecompletion = false;
if ( getMode() == cmLatex )
{
m_type = getType(entry.text);
if ( (m_type==CodeCompletion::ctReference && info()->allLabels()->count()>0) ||
(m_type==CodeCompletion::ctCitation && info()->allBibItems()->count()>0) )
{
m_keylistType = m_type;
m_ref = true;
m_completeTimer->start(20,true);
}
}
}
}
void CodeCompletion::slotCompleteValueList()
{
KILE_DEBUG() << "==slotCompleteValueList (" << m_kilecompletion << "," << m_inprogress << ")=============" << endl;
m_completeTimer->stop();
editCompleteList(getType());
}
void CodeCompletion::slotCompletionAborted()
{
KILE_DEBUG() << "==slotCompletionAborted (" << m_kilecompletion << "," << m_inprogress << ")=============" << endl;
CompletionAborted();
}
void CodeCompletion::slotFilterCompletion( KTextEditor::CompletionEntry* c, TQString *s )
{
KILE_DEBUG() << "==slotFilterCompletion (" << m_kilecompletion << "," << m_inprogress << ")=============" << endl;
if ( inProgress() ) // dani 28.09.2004
{
KILE_DEBUG() << "\tin progress: s=" << *s << endl;
*s = filterCompletionText( c->text, c->type );
KILE_DEBUG() << "\tfilter --->" << *s << endl;
m_inprogress = false;
m_kilecompletion = true;
}
}
void CodeCompletion::slotCharactersInserted(int, int, const TQString& string )
{
KILE_DEBUG() << "==slotCharactersInserted (m_kilecompletion=" << m_kilecompletion << "m_inprogress," << m_inprogress << ",m_ref=" << m_ref << ",string=" << string << ")=============" << endl;
if ( !inProgress() && m_autoDollar && string=="$" )
{
autoInsertDollar();
return;
}
// only work, if autocomplete mode of Kile is active
if ( !isActive() || !autoComplete() )
return ;
//FIXME this is not very efficient
m_view = info()->viewManager()->currentTextView();
// try to autocomplete abbreviations after punctuation symbol
if ( !inProgress() && m_autocompleteabbrev && completeAutoAbbreviation(string) )
return;
// rather unsusual, but it may happen: the cursor is inside
// of a reference command without a labellist.
if ( ! m_ref )
{
TQString startpattern;
CodeCompletion::Type reftype = insideReference(startpattern);
if ( reftype != CodeCompletion::ctNone )
{
m_ref = true;
editCompleteList(reftype,startpattern);
return;
}
} // also unusual but may also happen, 20.11.2008 tbraun
else if( m_ref && !inProgress())
{
TQString startpattern;
CodeCompletion::Type reftype = insideReference(startpattern);
if ( reftype == CodeCompletion::ctNone )
{
m_ref = false;
return;
}
}
TQString word;
Type type;
bool found = ( m_ref ) ? getReferenceWord(word) : getCompleteWord(true,word,type );
if ( found )
{
int wordlen = word.length();
KILE_DEBUG() << " auto completion: word=" << word << " mode=" << m_mode << " inprogress=" << inProgress() << endl;
if ( inProgress() ) // continue a running mode?
{
KILE_DEBUG() << " auto completion: continue current mode" << endl;
completeWord(word, m_mode);
}
else if ( word.at( 0 )=='\\' && m_autocomplete && wordlen>=m_latexthreshold)
{
KILE_DEBUG() << " auto completion: latex mode" << endl;
if ( string.at( 0 ).isLetter() )
{
completeWord(word, cmLatex);
}
else if ( string.at( 0 ) == '{' )
{
editCompleteList(type);
}
}
else if ( word.at(0).isLetter() && m_autocompletetext && wordlen>=m_textthreshold)
{
KILE_DEBUG() << " auto completion: document mode" << endl;
completeWord(word,cmDocumentWord);
}
}
}
//////////////////// testing characters (dani) ////////////////////
static bool isBackslash ( TQChar ch )
{
return ( ch == '\\' );
}
bool CodeCompletion::getCompleteWord(bool latexmode, TQString &text, Type &type )
{
if ( !m_view ) return false;
uint row, col;
TQChar ch;
// get current position
m_view->cursorPositionReal( &row, &col );
// there must be et least one sign
if ( col < 1 )
return "";
// get current text line
TQString textline = m_view->getDoc()->textLine( row );
//
int n = 0; // number of characters
int index = col; // go back from here
while ( --index >= 0 )
{
// get current character
ch = textline.at( index );
if ( ch.isLetter() || ch=='.' || ch == '_' || ch.isDigit() || ( latexmode && ( index + 1 == ( int ) col ) && ch == '{' ) )
++n; // accept letters and '{' as first character in latexmode
else
{
if ( latexmode && isBackslash( ch ) && oddBackslashes( textline, index ) ) // backslash?
++n;
break; // stop when a backslash was found
}
}
// select pattern and set type of match
text = textline.mid( col - n, n );
type = getType( text );
return !text.isEmpty();
}
bool CodeCompletion::getReferenceWord(TQString &text)
{
if ( !m_view ) return false;
uint row, col;
TQChar ch;
// get current position
m_view->cursorPositionReal( &row, &col );
// there must be et least one sign
if ( col < 1 )
return false;
// get current text line
TQString textline = m_view->getDoc()->textLine( row );
// search the current reference string
int pos = textline.findRev(reNotRefChars,col-1);
if ( pos < 0 )
pos = 0;
// select pattern
text = textline.mid(pos+1,col-1-pos);
return ( (uint)pos < col-1 );
}
void CodeCompletion::getDocumentWords(const TQString &text,
TQValueList<KTextEditor::CompletionEntry> &list)
{
KILE_DEBUG() << "getDocumentWords: " << endl;
list.clear();
TQRegExp reg("(\\\\?\\b" + TQString(text[0]) + "[^\\W\\d_]+)\\b");
Kate::Document *doc = m_view->getDoc();
TQString s;
KTextEditor::CompletionEntry e;
TQDict<bool> seen;
bool alreadyseen = true;
for (uint i=0; i<doc->numLines(); ++i) {
s = doc->textLine(i);
int pos = 0;
while ( pos >= 0 ) {
pos = reg.search(s,pos);
if ( pos >= 0 ) {
if ( reg.cap(1).at(0)!='\\' && text!=reg.cap(1) && !seen.find(reg.cap(1)) ) {
e.text = reg.cap(1); // normal entry
e.type = "";
list.append( e );
seen.insert(reg.cap(1),&alreadyseen);
}
pos += reg.matchedLength();
}
}
}
}
//////////////////// counting backslashes (dani) ////////////////////
bool CodeCompletion::oddBackslashes( const TQString& text, int index )
{
uint n = 0;
while ( index >= 0 && isBackslash( text.at( index ) ) )
{
++n;
--index;
}
return ( n % 2 ) ? true : false;
}
//////////////////// complete auto abbreviation ////////////////////
bool CodeCompletion::completeAutoAbbreviation(const TQString &text)
{
if ( text.length() != 1 )
return false;
TQChar ch = text[0];
if ( ! (ch.isSpace() || ch.isPunct()) )
return false;
uint row,col;
m_view->cursorPositionReal( &row, &col );
TQString abbrev = getAbbreviationWord(row,col-1);
if ( abbrev.isEmpty() )
return false;
TQString expansion = findExpansion(abbrev);
if ( expansion.isEmpty() )
return false;
KILE_DEBUG() << "=== CodeCompletion::completeAutoAbbreviation: abbrev=" << abbrev << " exp=" << expansion << endl;
uint len = abbrev.length();
uint startcol = col - len - 1;
Kate::Document *doc = m_view->getDoc();
doc->removeText( row,startcol,row,startcol+abbrev.length()+1 );
doc->insertText( row,startcol,expansion+ch);
m_view->setCursorPositionReal( row,startcol+expansion.length()+1 );
return true;
}
TQString CodeCompletion::getAbbreviationWord(uint row, uint col)
{
TQString textline = m_view->getDoc()->textLine( row );
int index = (int)col;
while ( --index >= 0 )
{
TQChar ch = textline.at( index );
if ( ! ch.isLetterOrNumber() )
break;
}
return textline.mid(index+1,col-index-1);
}
//////////////////// connection with the abbreviation listview ////////////////////
void CodeCompletion::setAbbreviationListview(KileAbbrevView *listview)
{
m_abbrevListview = listview;
connect( m_abbrevListview, TQT_SIGNAL(updateAbbrevList(const TQString &, const TQString &)),
this, TQT_SLOT(slotUpdateAbbrevList(const TQString &, const TQString &)) );
}
void CodeCompletion::slotUpdateAbbrevList(const TQString &ds, const TQString &as)
{
if ( ! ds.isEmpty() )
{
deleteAbbreviationEntry(ds);
}
if ( ! as.isEmpty() )
{
addAbbreviationEntry(as);
}
}
void CodeCompletion::deleteAbbreviationEntry( const TQString &entry )
{
KILE_DEBUG() << "=== CodeCompletion::deleteAbbreviationEntry (" << entry << ")" << endl;
TQValueList<KTextEditor::CompletionEntry>::Iterator it;
for ( it=m_abbrevlist.begin(); it!=m_abbrevlist.end(); ++it )
{
if ( (*it).text == entry )
{
m_abbrevlist.remove( it );
return;
}
}
}
void CodeCompletion::addAbbreviationEntry( const TQString &entry )
{
KILE_DEBUG() << "=== CodeCompletion::addAbbreviationEntry (" << entry << ")" << endl;
TQValueList<KTextEditor::CompletionEntry>::Iterator it;
for ( it=m_abbrevlist.begin(); it!=m_abbrevlist.end(); ++it )
{
if ( (*it).text > entry )
break;
}
KTextEditor::CompletionEntry e;
e.text = entry;
e.type = TQString();
if ( it == m_abbrevlist.begin() )
m_abbrevlist.prepend(e);
else if ( it == m_abbrevlist.end() )
m_abbrevlist.append(e);
else
m_abbrevlist.insert(it,e);
}
//////////////////// autoinsert $ ////////////////////
void CodeCompletion::autoInsertDollar()
{
Kate::View *view = info()->viewManager()->currentTextView();
if ( view )
{
uint row,col;
view = info()->viewManager()->currentTextView();
view->cursorPositionReal( &row, &col );
view->getDoc()->insertText(row,col,"$");
view->setCursorPositionReal( row, col );
}
}
}
#include "codecompletion.moc"