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/kileedit.cpp

2739 lines
67 KiB

/***************************************************************************
date : Mar 12 2007
version : 0.46
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 "kileedit.h"
#include <tqfileinfo.h>
#include <tqvaluestack.h>
#include <tqclipboard.h>
#include <tqapplication.h>
#include <kate/view.h>
#include <kate/document.h>
#include <tdetexteditor/searchinterface.h>
#include <tdetexteditor/editinterfaceext.h>
#include <tdelocale.h>
#include <kinputdialog.h>
#include <kstandarddirs.h>
#include "kilekonsolewidget.h"
#include "kileinfo.h"
#include "kileviewmanager.h"
#include "kileconfig.h"
#include "kileactions.h"
#include "kiletool_enums.h"
#include "kilelogwidget.h"
#include "kileextensions.h"
#include "quickpreview.h"
namespace KileDocument
{
EditorExtension::EditorExtension(KileInfo *info) : m_ki(info)
{
m_complete = new KileDocument::CodeCompletion(m_ki);
m_latexCommands = m_ki->latexCommands();
// init regexp
m_reg.setPattern("(\\\\(begin|end)\\s*\\{([A-Za-z]+\\*?)\\})|(\\\\\\[|\\\\\\])");
// 1 2 3 4
m_regexpEnter.setPattern("^(.*)((\\\\begin\\s*\\{([^\\{\\}]*)\\})|(\\\\\\[))");
// 1 23 4 5
// init double quotes
m_quoteList
<< "English quotes: `` ''"
<< "French quotes: \"< \">"
<< "German quotes: \"` \"'"
<< "French quotes (long): \\flqq \\frqq"
<< "German quotes (long): \\glqq \\grqq"
<< "Icelandic quotes (v1): \\ilqq \\irqq"
<< "Icelandic quotes (v2): \\iflqq \\ifrqq"
<< "Czech quotes: \\uv{ }"
;
readConfig();
}
EditorExtension::~EditorExtension()
{
delete m_complete;
}
//////////////////// read configuration ////////////////////
void EditorExtension::readConfig(void)
{
// init insertion of double quotes
initDoubleQuotes();
// calculate indent for autoindent of environments
m_envAutoIndent = TQString();
if ( KileConfig::envIndentation() )
{
if ( KileConfig::envIndentSpaces() )
{
int num = KileConfig::envIndentNumSpaces();
if ( num<1 || num>9 )
num = 1;
m_envAutoIndent.fill(' ',num);
}
else
{
m_envAutoIndent = "\t";
}
}
}
void EditorExtension::insertTag(const KileAction::TagData& data, Kate::View *view)
{
Kate::Document *doc = view->getDoc();
if ( !doc) return;
//whether or not to wrap tag around selection
bool wrap = ( (!data.tagEnd.isNull()) && doc->hasSelection());
//%C before or after the selection
bool before = data.tagBegin.contains("%C");
bool after = data.tagEnd.contains("%C");
//save current cursor position
int para=view->cursorLine();
int para_begin=para;
int index=view->cursorColumnReal();
int index_begin=index;
int para_end=0;
int index_cursor=index;
int para_cursor=index;
// offset for autoindentation of environments
int dxIndentEnv = 0;
// environment tag
bool envtag = data.tagBegin.contains("%E") || data.tagEnd.contains("%E");
TQString whitespace = getWhiteSpace( doc->textLine(para).left(index) );
//if there is a selection act as if cursor is at the beginning of selection
if (wrap)
{
index = doc->selStartCol();
para = doc->selStartLine();
para_end = doc->selEndLine();
}
TQString ins = data.tagBegin;
TQString tagEnd = data.tagEnd;
//start an atomic editing sequence
KTextEditor::EditInterfaceExt *editInterfaceExt = KTextEditor::editInterfaceExt( doc );
if ( editInterfaceExt ) editInterfaceExt->editBegin();
//cut the selected text
TQString trailing;
if (wrap)
{
TQString sel = doc->selection();
doc->removeSelectedText();
// no autoindentation of environments, when text is selected
if ( envtag )
{
ins.remove("%E");
tagEnd.remove("%E\n");
}
// strip one of two consecutive line ends
int len = sel.length();
if ( tagEnd.at(0)=='\n' && len>0 && sel.find('\n',-1)==len-1 )
sel.truncate( len-1 );
// now add the selection
ins += sel;
// place the cursor behind this tag, if there is no other wish
if ( !before && !after )
{
trailing = "%C";
after = true;
}
}
else if ( envtag )
{
ins.replace("%E",whitespace+m_envAutoIndent);
tagEnd.replace("%E",whitespace+m_envAutoIndent);
if ( data.dy > 0 )
dxIndentEnv = whitespace.length() + m_envAutoIndent.length();
}
tagEnd.replace("\\end{",whitespace+"\\end{");
ins += tagEnd + trailing;
//do some replacements
TQFileInfo fi( doc->url().path());
ins.replace("%S", fi.baseName(true));
ins.replace("%B", s_bullet);
//insert first part of tag at cursor position
doc->insertText(para,index,ins);
//move cursor to the new position
if ( before || after )
{
int n = data.tagBegin.contains("\n")+ data.tagEnd.contains("\n");
if (wrap) n += para_end > para ? para_end-para : para-para_end;
for (int line = para_begin; line <= para_begin+n; ++line)
{
if (doc->textLine(line).contains("%C"))
{
int i=doc->textLine(line).find("%C");
para_cursor = line; index_cursor = i;
doc->removeText(line,i,line,i+2);
break;
}
index_cursor=index;
para_cursor=line;
}
}
else
{
int py = para_begin, px = index_begin;
if (wrap) //act as if cursor was at beginning of selected text (which is the point where the tagBegin is inserted)
{
py = para;
px = index;
}
para_cursor = py+data.dy; index_cursor = px+data.dx+dxIndentEnv;
}
//end the atomic editing sequence
if ( editInterfaceExt ) editInterfaceExt->editEnd();
//set the cursor position (it is important that this is done outside of the atomic editing sequence)
view->setCursorPositionReal(para_cursor, index_cursor);
doc->clearSelection();
}
//////////////////// goto environment tag (begin or end) ////////////////////
// goto the next non-nested environment tag
Kate::View* EditorExtension::determineView(Kate::View *view)
{
if (view == 0L)
view = m_ki->viewManager()->currentTextView();
m_overwritemode = (view == 0L) ? false : view->isOverwriteMode();
return view;
}
void EditorExtension::gotoEnvironment(bool backwards, Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint row,col;
EnvData env;
bool found;
// get current position
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
// start searching
if ( backwards )
{
found = findBeginEnvironment(doc,row,col,env);
//KILE_DEBUG() << " goto begin env: " << env.row << "/" << env.col << endl;
}
else
{
found = findEndEnvironment(doc,row,col,env);
if ( !m_overwritemode )
env.col += env.len;
}
if ( found )
view->setCursorPositionReal(env.row,env.col);
}
// match the opposite environment tag
void EditorExtension::matchEnvironment(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint row,col;
EnvData env;
// get current position
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
// we only start, when we are at an environment tag
if ( !isEnvironmentPosition(doc,row,col,env) )
return;
gotoEnvironment( env.tag != EnvBegin,view);
}
//////////////////// close opened environments ////////////////////
// search for the last opened environment and close it
void EditorExtension::closeEnvironment(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint row,col,currentRow,currentCol;
TQString name;
view->cursorPositionReal(&currentRow,&currentCol);
if ( findOpenedEnvironment(row,col,name,view) )
{
if ( name == "\\[" )
view->getDoc()->insertText( currentRow,currentCol, "\\]" );
else
view->getDoc()->insertText( currentRow,currentCol, "\\end{" + name + '}' );
// view->setCursorPositionReal(row+1,0);
}
}
// close all opened environments
void EditorExtension::closeAllEnvironments(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
TQStringList envlist = findOpenedEnvironmentList(view,true);
if ( envlist.count() == 0 )
return;
uint currentRow,currentCol,outputCol;
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&currentRow,&currentCol);
bool indent = ! m_envAutoIndent.isEmpty();
if ( indent && currentCol > 0 )
{
doc->insertText(currentRow,currentCol,"\n");
currentRow++;
currentCol = 0;
}
bool ok1,ok2;
for ( TQStringList::Iterator it=envlist.begin(); it!=envlist.end(); ++it )
{
TQStringList entry = TQStringList::split(',',*it);
if ( entry[0] == "document" )
break;
uint row = entry[1].toUInt(&ok1);
uint col = entry[2].toUInt(&ok2);
if ( !ok1 || !ok2 )
continue;
outputCol = currentCol;
if ( indent )
{
TQString whitespace = getWhiteSpace( doc->textLine(row).left(col) );
doc->insertText(currentRow,outputCol,whitespace);
outputCol += whitespace.length();
}
TQString endtag = ( entry[0] == "\\[" ) ? "\\]\n" : "\\end{"+entry[0]+"}\n";
doc->insertText(currentRow,outputCol,endtag);
currentRow++;
}
}
//////////////////// mathgroup ////////////////////
void EditorExtension::selectMathgroup(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint row1,col1,row2,col2;
if ( getMathgroup(view,row1,col1,row2,col2) )
view->getDoc()->setSelection(row1,col1,row2,col2);
}
void EditorExtension::deleteMathgroup(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint row1,col1,row2,col2;
if ( getMathgroup(view,row1,col1,row2,col2) )
{
view->getDoc()->clearSelection();
view->getDoc()->removeText(row1,col1,row2,col2);
view->setCursorPosition(row1,0);
}
}
bool EditorExtension::hasMathgroup(Kate::View *view)
{
view = determineView(view);
if ( !view )
return false;
uint row1,col1,row2,col2;
return getMathgroup(view,row1,col1,row2,col2);
}
TQString EditorExtension::getMathgroupText(uint &row, uint &col, Kate::View *view)
{
uint row1,col1,row2,col2;
view = determineView(view);
if ( view && getMathgroup(view,row1,col1,row2,col2) )
{
row = row1;
col = col1;
return view->getDoc()->text(row1,col1,row2,col2);
}
else
return TQString();
}
bool EditorExtension::getMathgroup(Kate::View *view, uint &row1, uint &col1, uint &row2, uint &col2)
{
TQRegExp reg( TQString("\\$")
+ "|\\\\begin\\s*\\{([A-Za-z]+\\*?)\\}"
+ "|\\\\end\\s*\\{([A-Za-z]+\\*?)\\}"
+ "|\\\\\\[|\\\\\\]"
+ "|\\\\\\(|\\\\\\)"
);
uint row,col,r,c;
MathData begin,end;
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
TQString textline = getTextLineReal(doc,row);
// check for '\ensuremath{...}'
TQString word;
uint x1,x2;
if ( getCurrentWord(doc,row,col,smTex,word,x1,x2) && word=="\\ensuremath" )
view->setCursorPositionReal(row,x2);
BracketData open,close;
if ( getTexgroup(false,open,close,view) )
{
TQString s = getTextLineReal(doc,open.row);
if ( open.col>=11 && s.mid(open.col-11,11)=="\\ensuremath" )
{
view->setCursorPositionReal(row,col);
row1 = open.row;
col1 = open.col-11;
row2 = close.row;
col2 = close.col;
return true;
}
}
// do we need to restore the cursor position
view->setCursorPositionReal(row,col);
// '$' is difficult, because it is used as opening and closing tag
int mode = 0;
if ( textline[col] == '$' )
mode = 1;
else if ( col>0 && textline[col-1]=='$' )
mode = 2;
if ( mode > 0 )
{
// first look, if this is a closing '$'
r = row;
c = ( mode == 1 ) ? col : col-1;
if ( decreaseCursorPosition(doc,r,c) && findOpenMathTag(doc,r,c,reg,begin) )
{
if ( begin.tag==mmMathDollar && (begin.numdollar & 1) )
{
row1 = begin.row;
col1 = begin.col;
row2 = row;
col2 = ( mode == 1 ) ? col+1 : col;
return true;
}
}
// perhaps an opening '$'
r = row;
c = ( mode == 1 ) ? col+1 : col;
if ( findCloseMathTag(doc,r,c,reg,end) )
{
if ( end.tag==mmMathDollar )
{
row1 = row;
col1 = ( mode == 1 ) ? col : col-1;
row2 = end.row;
col2 = end.col + end.len;
return true;
}
}
// found no mathgroup with '$'
return false;
}
// now let's search for all other math tags:
// if a mathgroup tag starts in the current column, we save this
// position and move the cursor one column to the right
bool openingtag = isOpeningMathTagPosition(doc,row,col,begin);
if ( openingtag )
{
// try to find the corresponding closing tag at the right
bool closetag = findCloseMathTag(doc,row,col+1,reg,end);
if ( closetag && checkMathtags(begin,end) )
{
row1 = begin.row;
col1 = begin.col;
row2 = end.row;
col2 = end.col + end.len;
return true;
}
}
r = row;
c = col;
bool closingtag = isClosingMathTagPosition(doc,row,col,end);
if ( closingtag )
{
c = end.col;
if ( ! decreaseCursorPosition(doc,r,c) )
return false;
}
// now try to search to opening tag of the math group
if ( ! findOpenMathTag(doc,r,c,reg,begin) )
return false;
if ( begin.tag==mmMathDollar && !(begin.numdollar & 1) )
{
//KILE_DEBUG() << "error: even number of '$' --> no math mode" << endl;
return false;
}
// and now the closing tag
if ( ! findCloseMathTag(doc,r,c,reg,end) )
return false;
// both tags were found, but they must be of the same type
if ( checkMathtags(begin,end) )
{
row1 = begin.row;
col1 = begin.col;
row2 = end.row;
col2 = end.col + end.len;
return true;
}
else
return false;
}
//////////////////// mathgroup tags ////////////////////
bool EditorExtension::checkMathtags(const MathData &begin,const MathData &end)
{
// both tags were found, but they must be of the same type
if ( begin.tag != end.tag )
{
//KILE_DEBUG() << "error: opening and closing tag of mathmode don't match" << endl;
return false;
}
// and additionally: if it is a math env, both tags must have the same name
if ( begin.tag==mmDisplaymathEnv && begin.envname!=end.envname )
{
//KILE_DEBUG() << "error: opening and closing env tags have different names" << endl;
return false;
}
return true;
}
bool EditorExtension::isOpeningMathTagPosition(Kate::Document *doc, uint row, uint col, MathData &mathdata)
{
TQString textline = getTextLineReal(doc,row);
TQRegExp reg("\\\\begin\\s*\\{([A-Za-z]+\\*?)\\}|\\\\\\[|\\\\\\(");
if ( (int)col != reg.search(textline,col) )
return false;
TQChar id = reg.cap(0)[1];
TQString envname = reg.cap(1);
mathdata.row = row;
mathdata.col = col;
mathdata.len = reg.cap(0).length();
switch ( id )
{
case 'b' : if ( !(m_latexCommands->isMathEnv(envname) || envname=="math") || m_latexCommands->needsMathMode(envname) )
return false;
mathdata.tag = ( envname=="math" ) ? mmMathEnv : mmDisplaymathEnv;
mathdata.envname = envname;
break;
case '[' : mathdata.tag = mmDisplaymathParen;
break;
case '(' : mathdata.tag = mmMathParen;
break;
}
return true;
}
bool EditorExtension::isClosingMathTagPosition(Kate::Document *doc, uint row, uint col,MathData &mathdata)
{
TQString textline = doc->textLine(row);
TQRegExp reg("\\\\end\\s*\\{([A-Za-z]+\\*?)\\}|\\\\\\]|\\\\\\)");
int pos = reg.searchRev(textline,col);
if ( pos<0 || (int)col>pos+reg.matchedLength() )
return false;
TQChar id = reg.cap(0)[1];
TQString envname = reg.cap(1);
mathdata.row = row;
mathdata.col = pos;
mathdata.len = reg.cap(0).length();
switch ( id )
{
case 'e' : if ( !(m_latexCommands->isMathEnv(envname) || envname=="math") || m_latexCommands->needsMathMode(envname) )
return false;
mathdata.tag = ( envname=="math" ) ? mmMathEnv : mmDisplaymathEnv;
mathdata.envname = envname;
break;
case ']' : mathdata.tag = mmDisplaymathParen;
break;
case ')' : mathdata.tag = mmMathParen;
break;
}
return true;
}
bool EditorExtension::findOpenMathTag(Kate::Document *doc, uint row, uint col, TQRegExp &reg, MathData &mathdata)
{
uint lastrow,lastcol;
TQString mathname;
bool foundDollar= false;
uint numDollar = 0;
KTextEditor::SearchInterface *iface;
iface = dynamic_cast<KTextEditor::SearchInterface *>(doc);
TQString textline = getTextLineReal(doc,row);
int column = (int)col;
bool continueSearch = true;
while ( continueSearch )
{
while ( (column = reg.searchRev(textline,col)) != -1 )
{
col = column;
mathdata.row = row;
mathdata.col = col;
mathdata.len = reg.cap(0).length();
mathname = reg.cap(0).left(2);
// should be better called 'isValidChar()', because it checks for comments
// and escaped chars like backslash and dollar in '\\' and '\$'
if ( mathname == "$" )
{
// count and continue search
numDollar++;
// but remember the first dollar found backwards
if ( ! foundDollar )
{
lastrow = row;
lastcol = col;
foundDollar = true;
}
}
else if ( mathname=="\\[" || mathname=="\\(" )
{
// found start of mathmode
if ( numDollar == 0 )
{
mathdata.tag = ( mathname == "\\[" ) ? mmDisplaymathParen : mmMathParen;
mathdata.numdollar = 0;
return true;
}
else
{
//KILE_DEBUG() << "error: dollar not allowed in \\[ or \\( mode" << endl;
return false;
}
}
else if ( mathname=="\\]" || mathname=="\\)" )
{
continueSearch = false;
break;
}
else if ( mathname=="\\b" )
{
// save name of environment
TQString envname = reg.cap(1);
// if we found the opening tag of a math env
if ( m_latexCommands->isMathEnv(envname) || envname=="math" )
{
if ( numDollar > 0 )
{
//KILE_DEBUG() << "error: dollar not allowed in math env numdollar=" << numDollar << endl;
return false;
}
// if this is a math env with its own mathmode, we have found the starting position
if ( envname == "math" )
{
mathdata.tag = mmMathEnv;
mathdata.envname = envname;
return true;
}
if ( ! m_latexCommands->needsMathMode(envname) )
{
mathdata.tag = mmDisplaymathEnv;
mathdata.envname = envname;
return true;
}
}
// no math env, we found the opening tag of a normal env
else
{
continueSearch = false;
break;
}
}
else if ( mathname == "\\e" )
{
TQString envname = reg.cap(2);
// if we found the closing tag of a math env
if ( m_latexCommands->isMathEnv(envname) || envname=="math" )
{
// if this is a math env with its own mathmode
if ( ! m_latexCommands->needsMathMode(envname) || envname=="math" )
{
continueSearch = false;
break;
}
// if this is a math env which needs $..$
if ( m_latexCommands->isMathModeEnv(envname) )
{
if ( numDollar >= 1 )
{
numDollar--;
continueSearch = false;
break;
}
// else continue search
}
}
// if we found the closing tag of a normal env
else
{
continueSearch = false;
break;
}
}
else
{
//KILE_DEBUG() << "error: unknown match" << endl;
return false;
}
// continue search one column left of the last match (if this is possible)
if ( col == 0 )
break;
col--;
}
if ( row > 0 )
{
textline = getTextLineReal(doc,--row);
col = textline.length();
}
if ( column == -1 )
{
continueSearch = false;
break;
}
}
// nothing else found, so math mode starts a the last dollar (the first one found backwards)
mathdata.row = lastrow;
mathdata.col = lastcol;
mathdata.len = 1;
mathdata.numdollar = numDollar;
mathdata.tag = ( numDollar > 0 ) ? mmMathDollar : mmNoMathMode;
return true;
}
bool EditorExtension::findCloseMathTag(Kate::Document *doc, uint row, uint col, TQRegExp &reg, MathData &mathdata)
{
KTextEditor::SearchInterface *iface;
iface = dynamic_cast<KTextEditor::SearchInterface *>(doc);
uint rowFound,colFound,lenFound;
while ( iface->searchText(row,col,reg,&rowFound,&colFound,&lenFound,false) )
{
// should be better called 'isValidChar()', because it checks for comments
// and escaped chars like backslash and dollar in '\\' and '\$'
if ( isValidBackslash(doc,rowFound,colFound) )
{
TQString mathname = reg.cap(0).left(2);
// always remember behind the last match
mathdata.row = rowFound;
mathdata.col = colFound;
mathdata.len = lenFound;
if ( mathname=="$" )
{
mathdata.tag = mmMathDollar;
return true;
}
else if ( mathname=="\\]" )
{
mathdata.tag = mmDisplaymathParen;
return true;
}
else if ( mathname=="\\)" )
{
mathdata.tag = mmMathParen;
return true;
}
else if ( mathname=="\\[" || mathname=="\\(" )
{
//KILE_DEBUG() << "error: current mathgroup was not closed" << endl;
return false;
}
else if ( mathname=="\\b" )
{
TQString envname = reg.cap(1);
if ( ! (m_latexCommands->isMathEnv(envname) || envname=="math") )
{
//KILE_DEBUG() << "error: only math env are allowed in mathmode (found begin tag)" << endl;
return false;
}
if ( !m_latexCommands->needsMathMode(envname) || envname=="math" )
{
//KILE_DEBUG() << "error: mathenv with its own mathmode are not allowed in mathmode " << endl;
return false;
}
// else continue search
}
else if ( mathname=="\\e" )
{
TQString envname = reg.cap(2);
if ( ! (m_latexCommands->isMathEnv(envname) || envname=="math") )
{
//KILE_DEBUG() << "error: only math env are allowed in mathmode (found end tag)" << endl;
return false;
}
if ( envname == "math" )
{
mathdata.tag = mmMathEnv;
mathdata.envname = envname;
return true;
}
if ( ! m_latexCommands->needsMathMode(envname) )
{
mathdata.tag = mmDisplaymathEnv;
mathdata.envname = envname;
return true;
}
// else continue search
}
}
// continue search one column right of the last match (if this is possible)
row = rowFound;
col = colFound;
if ( ! increaseCursorPosition(doc,row,col) )
break;
}
return false;
}
//////////////////// insert newlines inside an environment ////////////////////
// intelligent newlines: look for the last opened environment
// and decide what to insert
// or continue the comment
void EditorExtension::insertIntelligentNewline(Kate::View *view)
{
KILE_DEBUG() << "void EditorExtension::insertIntelligentNewline(Kate::View *view)" << endl;
view = determineView(view);
if ( !view )
return;
Kate::Document* doc = view->getDoc();
if( !doc )
return;
uint row,col;
TQString name;
view->cursorPositionReal(&row,&col);
if(isCommentPosition(doc,row,col))
{
KILE_DEBUG() << "found comment" << endl;
view->keyReturn();
view->insertText("% ");
return;
}
else if ( findOpenedEnvironment(row,col,name,view) )
{
if ( m_latexCommands->isListEnv(name) )
{
view->keyReturn();
if ( name == "description" )
{
view->insertText("\\item[]");
view->cursorLeft();
}
else
view->insertText("\\item ");
return;
}
else if ( m_latexCommands->isTabularEnv(name) || m_latexCommands->isMathEnv(name) )
{
view->insertText(" \\\\");
}
}
// - no comment position
// - found no opened environment
// - unknown environment
// - finish tabular or math environment
view->keyReturn();
}
bool EditorExtension::findOpenedEnvironment(uint &row,uint &col, TQString &envname, Kate::View *view)
{
view = determineView(view);
if ( !view ) return false;
// get current cursor position
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
EnvData env;
uint startrow = row;
uint startcol = col;
//KILE_DEBUG() << " close - start " << endl;
// accept a starting place outside an environment
bool env_position = isEnvironmentPosition(doc,row,col,env);
// We can also accept a column, if we are on the left side of an environment.
// But we should decrease the current cursor position for the search.
if ( env_position && env.cpos!=EnvInside )
{
if ( env.cpos==EnvLeft && !decreaseCursorPosition(doc,startrow,startcol) )
return false;
env_position = false;
}
if ( !env_position && findEnvironmentTag(doc,startrow,startcol,env,true) )
{
//KILE_DEBUG() << " close - found begin env at: " << env.row << "/" << env.col << " " << env.name << endl;
row = env.row;
col = env.col;
envname = env.name;
return true;
}
else
return false;
}
TQStringList EditorExtension::findOpenedEnvironmentList(Kate::View *view, bool position)
{
TQStringList envlist;
view = determineView(view);
if ( view )
{
uint currentRow,currentCol;
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&currentRow,&currentCol);
uint row = currentRow;
uint col = currentCol;
EnvData env;
// check the starting position
bool env_position = isEnvironmentPosition(doc,row,col,env);
if ( env_position )
{
// we are inside an environment tag: bad to complete
if ( env.cpos == EnvInside )
return envlist;
// we are left of an environment tag: go one position to the left
if ( env.cpos == EnvLeft )
{
if ( ! decreaseCursorPosition(doc,row,col) )
return envlist;
}
}
while ( findEnvironmentTag(doc,row,col,env,true) )
{
row = env.row;
col = env.col;
if ( position )
envlist << env.name + TQString(",%1,%2").arg(row).arg(col);
else
envlist << env.name;
if ( col == 0 )
{
if ( ! decreaseCursorPosition(doc,row,col) )
break;
}
view->setCursorPositionReal(row,col);
}
// reset cursor original position
view->setCursorPositionReal(currentRow,currentCol);
}
return envlist;
}
//////////////////// select an environment ////////////////////
void EditorExtension::selectEnvironment(bool inside, Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
EnvData envbegin,envend;
if ( !view->getDoc()->hasSelection() || !expandSelectionEnvironment(inside,view) )
{
if ( getEnvironment(inside,envbegin,envend,view) )
view->getDoc()->setSelection(envbegin.row,envbegin.col,envend.row,envend.col);
}
}
void EditorExtension::deleteEnvironment(bool inside, Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
EnvData envbegin,envend;
if ( getEnvironment(inside,envbegin,envend,view) )
{
Kate::Document *doc = view->getDoc();
doc->clearSelection();
doc->removeText(envbegin.row,envbegin.col,envend.row,envend.col);
view->setCursorPosition(envbegin.row,0);
}
}
// calculate start and end of an environment
bool EditorExtension::getEnvironment(bool inside, EnvData &envbegin, EnvData &envend, Kate::View *view)
{
view = determineView(view);
if ( !view ) return false;
uint row,col;
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
if ( !findBeginEnvironment(doc,row,col,envbegin) )
return false;
if ( !findEndEnvironment(doc,row,col,envend) )
return false;
if ( inside )
{
// check first line
envbegin.col += envbegin.len;
if ( envbegin.col >= (uint)doc->lineLength(envbegin.row) )
{
++envbegin.row;
envbegin.col = 0;
}
}
else
{
envend.col += envend.len;
// check last line
if ( envbegin.col==0 && envend.col==(uint)doc->lineLength(envend.row) )
{
++envend.row;
envend.col = 0;
}
}
return true;
}
// determine text, startrow and startcol of current environment
TQString EditorExtension::getEnvironmentText(uint &row, uint &col, TQString &name, Kate::View *view)
{
view = determineView(view);
if ( !view ) return TQString();
EnvData envbegin,envend;
if ( getEnvironment(false,envbegin,envend,view) && envbegin.name!="document" )
{
row = envbegin.row;
col = envbegin.col;
name = envbegin.name;
return view->getDoc()->text(envbegin.row,envbegin.col,envend.row,envend.col);
}
else
{
return TQString();
}
}
bool EditorExtension::hasEnvironment(Kate::View *view)
{
view = determineView(view);
if ( !view )
return false;
EnvData envbegin,envend;
return ( getEnvironment(false,envbegin,envend,view) && envbegin.name!="document" );
}
// when an environment is selected (inside or outside),
// the selection is expanded to the surrounding environment
bool EditorExtension::expandSelectionEnvironment(bool inside, Kate::View *view)
{
view = determineView(view);
if ( !view ) return false;
Kate::Document *doc = view->getDoc();
if ( ! doc->hasSelection() )
return false;
// get current position
uint row,col;
view->cursorPositionReal(&row,&col);
// get current selection
uint row1 = doc->selStartLine();
uint col1 = doc->selStartCol();
uint row2 = doc->selEndLine();
uint col2 = doc->selEndCol();
// determine current environment outside
EnvData oenvbegin,oenvend;
if ( ! getEnvironment(false,oenvbegin,oenvend,view) )
return false;
bool newselection = false;
// first look, if this environment is selected outside
if ( row1==oenvbegin.row && col1==oenvbegin.col && row2==oenvend.row && col2==oenvend.col ) {
if ( ! decreaseCursorPosition(doc,oenvbegin.row,oenvbegin.col) )
return newselection;
view->setCursorPositionReal(oenvbegin.row,oenvbegin.col);
// search the surrounding environment and select it
if ( getEnvironment(inside,oenvbegin,oenvend,view) ) {
doc->setSelection(oenvbegin.row,oenvbegin.col,oenvend.row,oenvend.col);
newselection = true;
}
} else {
// then determine current environment inside
EnvData ienvbegin,ienvend;
getEnvironment(true,ienvbegin,ienvend,view);
// and look, if this environment is selected inside
if ( row1==ienvbegin.row && col1==ienvbegin.col && row2==ienvend.row && col2==ienvend.col ) {
if ( ! decreaseCursorPosition(doc,oenvbegin.row,oenvbegin.col) )
return newselection;
view->setCursorPositionReal(oenvbegin.row,oenvbegin.col);
// search the surrounding environment and select it
if ( getEnvironment(inside,ienvbegin,ienvend,view) ) {
doc->setSelection(ienvbegin.row,ienvbegin.col,ienvend.row,ienvend.col);
newselection = true;
}
}
}
// restore old cursor position
view->setCursorPositionReal(row,col);
return newselection;
}
//////////////////// search for \begin{env} ////////////////////
// Find the last \begin{env} tag. If the current cursor is over
// - \begin{env} tag: we will stop immediately
// - \end{env} tag: we will start before this tag
bool EditorExtension::findBeginEnvironment(Kate::Document *doc, uint row, uint col,EnvData &env)
{
// KILE_DEBUG() << " find begin: " << endl;
if ( isEnvironmentPosition(doc,row,col,env) )
{
// already found position?
//KILE_DEBUG() << " found env at: " << env.row << "/" << env.col << " " << env.name << endl;
if ( env.tag == EnvBegin )
{
//KILE_DEBUG() << " is begin env at: " << env.row << "/" << env.col << " " << env.name << endl;
return true;
}
// go one position back
//KILE_DEBUG() << " is end env at: " << env.row << "/" << env.col << " " << env.name << endl;
row = env.row;
col = env.col;
if ( ! decreaseCursorPosition(doc,row,col) )
return false;
}
// looking back for last environment
//KILE_DEBUG() << " looking back from pos: " << row << "/" << col << " " << env.name << endl;
return findEnvironmentTag(doc,row,col,env,true);
}
//////////////////// search for \end{env} ////////////////////
// Find the last \end{env} tag. If the current cursor is over
// - \end{env} tag: we will stop immediately
// - \begin{env} tag: we will start behind this tag
bool EditorExtension::findEndEnvironment(Kate::Document *doc, uint row, uint col,EnvData &env)
{
if ( isEnvironmentPosition(doc,row,col,env) )
{
// already found position?
if ( env.tag == EnvEnd )
return true;
// go one position forward
row = env.row;
col = env.col + 1;
}
// looking forward for the next environment
return findEnvironmentTag(doc,row,col,env,false);
}
//////////////////// search for an environment tag ////////////////////
// find the last/next non-nested environment tag
bool EditorExtension::findEnvironmentTag(Kate::Document *doc, uint row, uint col,
EnvData &env,bool backwards)
{
KTextEditor::SearchInterface *iface;
iface = dynamic_cast<KTextEditor::SearchInterface *>(doc);
uint envcount = 0;
EnvTag wrong_env = ( backwards ) ? EnvEnd : EnvBegin;
while ( iface->searchText(row,col,m_reg,&env.row,&env.col,&env.len,backwards) )
{
if ( isValidBackslash(doc,env.row,env.col) )
{
EnvTag found_env = ( m_reg.cap(2)=="begin" || m_reg.cap(4)=="\\[" ) ? EnvBegin : EnvEnd;
if ( found_env == wrong_env )
{
++envcount;
}
else
{
if ( envcount > 0 )
--envcount;
else
{
if ( found_env == EnvBegin )
{
env.name = ( m_reg.cap(2)=="begin" ) ? m_reg.cap(3) : "\\[";
}
else
{
env.name = ( m_reg.cap(2)=="end" ) ? m_reg.cap(3) : "\\]";
}
env.tag = found_env;
return true;
}
}
}
// new start position
if ( !backwards )
{
row = env.row;
col = env.col + 1;
}
else
{
row = env.row;
col = env.col;
if ( ! decreaseCursorPosition(doc,row,col) )
return false;
}
}
return false;
}
//////////////////// check for an environment position ////////////////////
// Check if the current position belongs to an environment. The result is set
// to the beginning backslash of the environment tag. The same algorithms as
// matching brackets is used.
bool EditorExtension::isEnvironmentPosition(Kate::Document *doc, uint row, uint col, EnvData &env)
{
// get real textline without comments, quoted characters and pairs of backslashes
TQString textline = getTextLineReal(doc,row);
if ( col > textline.length() )
return false;
EnvData envright;
bool left = false;
bool right = false;
//KTextEditor::SearchInterface *iface;
//iface = dynamic_cast<KTextEditor::SearchInterface *>(doc);
// check if there is a match in this line from the current position to the left
int startcol = ( textline[col] == '\\' ) ? col - 1 : col;
if ( startcol >= 1 )
{
int pos = textline.findRev(m_reg,startcol);
env.len = m_reg.matchedLength();
if ( pos!=-1 && (uint)pos<col && col<=(uint)pos+env.len )
{
env.row = row;
env.col = pos;
TQChar ch = textline.at(pos+1);
if ( ch=='b' || ch=='e' )
{
env.tag = ( ch == 'b' ) ? EnvBegin : EnvEnd;
env.name = m_reg.cap(3);
}
else
{
env.tag = ( ch == '[' ) ? EnvBegin : EnvEnd;
env.name = m_reg.cap(4);
}
env.cpos = ( col < (uint)pos+env.len ) ? EnvInside : EnvRight;
// we have already found a tag, if the cursor is inside, but not behind this tag
if ( env.cpos == EnvInside )
return true;
left = true;
//KILE_DEBUG() << " is - found left: pos=" << pos << " " << env.name << " " << TQString(textline.at(pos+1)) << endl;
}
}
// check if there is a match in this line from the current position to the right
if ( textline[col]=='\\' && col==(uint)textline.find(m_reg,col) )
{
envright.row = row;
envright.col = col;
envright.len = m_reg.matchedLength();
TQChar ch = textline.at(col+1);
if ( ch=='b' || ch=='e' ) // found "\begin" or "\end"
{
envright.tag = ( ch == 'b' ) ? EnvBegin : EnvEnd;
envright.name = m_reg.cap(3);
}
else // found "\[" or "\\]"
{
envright.tag = ( ch == '[' ) ? EnvBegin : EnvEnd;
envright.name = m_reg.cap(4);
}
envright.cpos = EnvLeft;
right = true;
//KILE_DEBUG() << " is - found right: pos=" <<col << " " << envright.name << " " << TQString(textline.at(col+1)) << endl;
}
//KILE_DEBUG() << "found left/right: " << left << "/" << right << endl;
// did we find a tag?
if ( ! (left || right) )
return false;
// now check, which tag we should be taken (algorithm like matching brackets)
if ( m_overwritemode )
{
if ( right && envright.tag==EnvBegin )
{
env = envright;
return true;
}
else if ( left && env.tag==EnvEnd )
return true;
else
return false;
}
else if ( left && env.tag==EnvEnd )
{
//KILE_DEBUG() << " 1: accept left end: " << env.name << endl;
return true;
}
else if ( right && envright.tag==EnvBegin )
{
//KILE_DEBUG() << " 2: accept right begin: " << envright.name << endl;
env = envright;
}
else if ( left && env.tag==EnvBegin )
{
// KILE_DEBUG() << " 3: accept left begin: " << env.name << endl;
return true;
}
else if ( right && envright.tag==EnvEnd )
{
//KILE_DEBUG() << " 4: accept right end: " << envright.name << endl;
env = envright;
}
else
return false;
return true;
}
//////////////////// check for a comment ////////////////////
// check if the current position is within a comment
bool EditorExtension::isCommentPosition(Kate::Document *doc, uint row, uint col)
{
TQString textline = doc->textLine(row);
bool backslash = false;
for ( uint i=0; i<col; ++i )
{
if ( textline[i] == '%' )
{
if ( !backslash )
return true; // found a comment sign
else
backslash = false;
}
else if ( textline[i] == '\\' ) // count number of backslashes
backslash = !backslash;
else
backslash = false; // no backslash
}
return false;
}
// check if the character at text[col] is a valid backslash:
// - there is no comment sign in this line before
// - there is not a odd number of backslashes directly before
bool EditorExtension::isValidBackslash(Kate::Document *doc, uint row, uint col)
{
TQString textline = doc->textLine(row);
bool backslash = false;
for ( uint i=0; i<col; ++i )
{
if ( textline[i] == '%' )
{
if ( !backslash )
return false; // found a comment sign
else
backslash = false;
}
else if ( textline[i] == '\\' ) // count number of backslashes
backslash = !backslash;
else
backslash = false; // no backslash
}
return !backslash;
}
//////////////////// goto next bullet ////////////////////
void EditorExtension::gotoBullet(bool backwards, Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint row,col,ypos,xpos,len;
// get current position
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
// change the start position or we will stay at this place
if ( backwards )
{
if ( ! decreaseCursorPosition(doc,row,col) )
return;
}
else
{
if ( ! increaseCursorPosition(doc,row,col) )
return;
}
if ( doc->searchText(row,col,s_bullet,&ypos,&xpos,&len,true,backwards) )
{
doc->setSelection(ypos,xpos,ypos,xpos+1);
view->setCursorPositionReal(ypos,xpos);
}
}
//////////////////// increase/decrease cursor position ////////////////////
bool EditorExtension::increaseCursorPosition(Kate::Document *doc, uint &row, uint &col)
{
bool ok = true;
if ( (int)col < doc->lineLength(row)-1 )
++col;
else if ( row < doc->numLines() - 1 )
{
++row;
col=0;
}
else
ok = false;
return ok;
}
bool EditorExtension::decreaseCursorPosition(Kate::Document *doc, uint &row, uint &col)
{
bool ok = true;
if (col > 0)
--col;
else if ( row > 0 )
{
--row;
col = doc->lineLength(row);
}
else
ok = false;
return ok;
}
//////////////////// texgroups ////////////////////
// goto the next non-nested bracket
void EditorExtension::gotoTexgroup(bool backwards, Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint row,col;
bool found;
BracketData bracket;
// get current position
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
m_overwritemode = view->isOverwriteMode();
// start searching
if ( backwards )
found = findOpenBracket(doc,row,col,bracket);
else
{
found = findCloseBracket(doc,row,col,bracket);
// go behind the bracket
if ( ! m_overwritemode )
++bracket.col;
}
if ( found )
view->setCursorPositionReal(bracket.row,bracket.col);
}
// match the opposite bracket
void EditorExtension::matchTexgroup(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint row,col;
BracketData bracket;
// get current position
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
m_overwritemode = view->isOverwriteMode();
// this operation is only allowed at a bracket position
if ( !isBracketPosition(doc,row,col,bracket) )
return;
// start searching
bool found = false;
if ( bracket.open )
{
found = findCloseBracketTag(doc,bracket.row,bracket.col+1,bracket);
// go behind the bracket
if ( ! m_overwritemode )
++bracket.col;
}
else
{
if ( !decreaseCursorPosition(doc,bracket.row,bracket.col) )
return;
found = findOpenBracketTag(doc,bracket.row,bracket.col,bracket);
}
if ( found )
view->setCursorPositionReal(bracket.row,bracket.col);
}
//////////////////// close an open texgroup ////////////////////
// search for the last opened texgroup and close it
void EditorExtension::closeTexgroup(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint row,col;
BracketData bracket;
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
uint rowtemp = row;
uint coltemp = col;
if ( !decreaseCursorPosition(doc,rowtemp,coltemp) )
return;
if ( findOpenBracketTag(doc,rowtemp,coltemp,bracket) )
{
doc->insertText( row,col,"}" );
view->setCursorPositionReal(row,col+1);
}
}
//////////////////// select a texgroup ////////////////////
void EditorExtension::selectTexgroup(bool inside, Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
BracketData open,close;
if ( getTexgroup(inside,open,close,view) )
{
Kate::Document *doc = view->getDoc();
doc->setSelection(open.row,open.col,close.row,close.col);
}
}
void EditorExtension::deleteTexgroup(bool inside, Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
BracketData open,close;
if ( getTexgroup(inside,open,close,view) )
{
Kate::Document *doc = view->getDoc();
doc->clearSelection();
doc->removeText(open.row,open.col,close.row,close.col);
view->setCursorPositionReal(open.row,open.col+1);
}
}
// calculate start and end of an environment
bool EditorExtension::getTexgroup(bool inside, BracketData &open, BracketData &close, Kate::View *view)
{
view = determineView(view);
if ( !view ) return false;
uint row,col;
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
if ( !findOpenBracket(doc,row,col,open) )
{
//KILE_DEBUG() << "no open bracket" << endl;
return false;
}
if ( !findCloseBracket(doc,row,col,close) )
{
//KILE_DEBUG() << "no close bracket" << endl;
return false;
}
if ( inside )
++open.col;
else
++close.col;
return true;
}
//////////////////// search for a bracket position ////////////////////
// Find the last opening bracket. If the current cursor is over
// - '{': we will stop immediately
// - '}': we will start before this character
bool EditorExtension::findOpenBracket(Kate::Document *doc, uint row, uint col, BracketData &bracket)
{
if ( isBracketPosition(doc,row,col,bracket) )
{
// already found position?
if ( bracket.open ) return true;
// go one position back
row = bracket.row;
col = bracket.col;
if ( ! decreaseCursorPosition(doc,row,col) ) return false;
}
// looking back for last bracket
return findOpenBracketTag(doc,row,col,bracket);
}
// Find the last closing bracket. If the current cursor is over
// - '}': we will stop immediately
// - '{': we will start behind this character
bool EditorExtension::findCloseBracket(Kate::Document *doc, uint row, uint col, BracketData &bracket)
{
if ( isBracketPosition(doc,row,col,bracket) )
{
// already found position?
if ( ! bracket.open ) return true;
// go one position forward
row = bracket.row;
col = bracket.col + 1;
}
// looking forward for next bracket
return findCloseBracketTag(doc,row,col,bracket);
}
/*
Bracket matching uses the following algorithm (taken from Kate):
1) If in overwrite mode, match the bracket currently underneath the cursor.
2) Otherwise, if the character to the left of the cursor is an ending bracket,
match it.
3) Otherwise if the character to the right of the cursor is a
starting bracket, match it.
4) Otherwise, if the the character to the left of the cursor is an
starting bracket, match it.
5) Otherwise, if the character to the right of the cursor is an
ending bracket, match it.
6) Otherwise, don't match anything.
*/
bool EditorExtension::isBracketPosition(Kate::Document *doc, uint row, uint col, BracketData &bracket)
{
// default results
bracket.row = row;
bracket.col = col;
TQString textline = getTextLineReal(doc,row);
TQChar right = textline[col];
TQChar left = ( col > 0 ) ? textline[col-1] : TQChar(' ');
if ( m_overwritemode )
{
if ( right == '{' )
{
bracket.open = true;
}
else if ( left == '}' )
{
bracket.open = false;
}
else
return false;
}
else if ( left == '}' )
{
bracket.open = false;
--bracket.col;
}
else if ( right == '{' )
{
bracket.open = true;
}
else if ( left == '{' )
{
bracket.open = true;
--bracket.col;
}
else if ( right == '}' )
{
bracket.open = false;
}
else
return false;
return true;
}
// find next non-nested closing bracket
bool EditorExtension::findCloseBracketTag(Kate::Document *doc, uint row, uint col,BracketData &bracket)
{
uint brackets = 0;
for ( uint line=row; line<doc->numLines(); ++line )
{
uint start = ( line == row ) ? col : 0;
TQString textline = getTextLineReal(doc,line);
for ( uint i=start; i<textline.length(); ++i )
{
if ( textline[i] == '{' )
{
++brackets;
}
else if ( textline[i] == '}' )
{
if ( brackets > 0 )
--brackets;
else
{
bracket.row = line;
bracket.col = i;
bracket.open = false;
return true;
}
}
}
}
return false;
}
// find next non-nested opening bracket
bool EditorExtension::findOpenBracketTag(Kate::Document *doc, uint row, uint col, BracketData &bracket)
{
uint brackets = 0;
for ( int line=row; line>=0; --line )
{
TQString textline = getTextLineReal(doc,line);
int start = ( line == (int)row ) ? col : textline.length()-1;
for ( int i=start; i>=0; --i )
{
//KILE_DEBUG() << "findOpenBracketTag: (" << line << "," << i << ") = " << textline[i].latin1() << endl;
if ( textline[i] == '{' )
{
if ( brackets > 0 )
--brackets;
else
{
bracket.row = line;
bracket.col = i;
bracket.open = true;
return true;
}
}
else if ( textline[i] == '}' )
{
++brackets;
}
}
}
//KILE_DEBUG() << "nothting found" << endl;
return false;
}
//////////////////// get real text ////////////////////
// get current textline and remove
// - all pairs of backslashes: '\\'
// - all quoted comment signs: '\%'
// - all quoted brackets: '\{' and '\}'
// - all comments
// replace these characters one one, which never will be looked for
TQString EditorExtension::getTextLineReal(Kate::Document *doc, uint row)
{
TQString textline = doc->textLine(row);
uint len = textline.length();
if ( len == 0)
return TQString();
bool backslash = false;
for (uint i=0; i<len; ++i )
{
if ( textline[i]=='{' || textline[i]=='}' || textline[i]=='$')
{
if ( backslash )
{
textline[i-1] = '&';
textline[i] = '&';
}
backslash = false;
}
else if ( textline[i]=='\\' )
{
if ( backslash )
{
textline[i-1] = '&';
textline[i] = '&';
backslash = false;
}
else
backslash = true;
}
else if ( textline[i]=='%' )
{
if ( backslash )
{
textline[i-1] = '&';
textline[i] = '&';
}
else
{
len = i;
break;
}
backslash = false;
}
else
backslash = false;
}
// return real text
return textline.left(len);
}
//////////////////// capture the current word ////////////////////
// Capture the current word from the cursor position to the left and right.
// The result depens on the given search mode;
// - smTex only letters, except backslash as first and star as last character
// - smLetter: only letters
// - smWord: letters and digits
// - smNospace: everything except white space
bool EditorExtension::getCurrentWord(Kate::Document *doc, uint row, uint col, EditorExtension::SelectMode mode, TQString &word,uint &x1,uint &x2)
{
// get real textline without comments, quoted characters and pairs of backslashes
TQString textline = getTextLineReal(doc,row);
if ( col > textline.length() )
return false;
TQRegExp reg;
TQString pattern1,pattern2;
switch ( mode )
{
case smLetter :
pattern1 = "[^a-zA-Z]+";
pattern2 = "[a-zA-Z]+";
break;
case smWord :
pattern1 = "[^a-zA-Z0-9]";
pattern2 = "[a-zA-Z0-9]+";
break;
case smNospace:
pattern1 = "\\s";
pattern2 = "\\S+";
break;
default :
pattern1 = "[^a-zA-Z]";
pattern2 = "\\\\?[a-zA-Z]+\\*?";
break;
}
x1 = x2 = col;
int pos;
// search to the left side
if ( col > 0 )
{
reg.setPattern(pattern1);
pos = textline.findRev(reg,col-1);
if ( pos != -1 ) { // found an illegal character
x1 = pos + 1;
if ( mode == smTex ) {
if ( textline[pos] == '\\' )
x1 = pos;
col = x1;
}
} else {
x1 = 0; // pattern matches from beginning of line
}
}
// search at the current position
reg.setPattern(pattern2);
pos = textline.find(reg,col);
if ( pos!=-1 && (uint)pos==col )
{
x2 = pos + reg.matchedLength();
}
// get all characters
if ( x1 != x2 )
{
word = textline.mid(x1,x2-x1);
return true;
}
else
return false;
}
//////////////////// paragraph ////////////////////
void EditorExtension::selectParagraph(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint startline,endline;
if ( findCurrentTexParagraph(startline,endline,view) )
{
view->getDoc()->setSelection(startline,0,endline+1,0);
}
}
void EditorExtension::deleteParagraph(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint startline,endline;
if ( findCurrentTexParagraph(startline,endline,view) )
{
Kate::Document *doc = view->getDoc();
doc->clearSelection();
if ( startline > 0 )
--startline;
else if ( endline < doc->numLines()-1 )
++endline;
doc->removeText(startline,0,endline+1,0);
view->setCursorPosition(startline,0);
}
}
// get the range of the current paragraph
bool EditorExtension::findCurrentTexParagraph(uint &startline, uint &endline, Kate::View *view)
{
view = determineView(view);
if ( !view ) return false;
uint row,col;
// get current position
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
// don't accept an empty line as part of a paragraph
if ( doc->textLine(row).stripWhiteSpace().isEmpty() )
return false;
// settings default results
startline = row;
endline = row;
// find the previous empty line
for ( int line=row-1; line>=0; --line )
{
if ( doc->textLine(line).stripWhiteSpace().isEmpty() )
break;
startline = line;
}
// find the next empty line
for ( uint line=row+1; line<doc->numLines(); ++line )
{
if ( doc->textLine(line).stripWhiteSpace().isEmpty() )
break;
endline = line;
}
// settings result
return true;
}
void EditorExtension::gotoNextParagraph(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
bool found;
uint startline,endline;
Kate::Document *doc = view->getDoc();
endline = view->cursorLine();
if ( doc->textLine(endline).stripWhiteSpace().isEmpty() )
found = true;
else
found = findCurrentTexParagraph(startline,endline,view);
// we are in an empty line or in the last line of a paragraph
if ( found )
{
// find the next non empty line
for ( uint line=endline+1; line<doc->numLines(); ++line )
{
if ( ! doc->textLine(line).stripWhiteSpace().isEmpty() )
{
view->setCursorPositionReal(line,0);
return;
}
}
}
}
void EditorExtension::gotoPrevParagraph(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
bool found;
uint startline,endline;
Kate::Document *doc = view->getDoc();
startline = view->cursorLine();
if ( doc->textLine(startline).stripWhiteSpace().isEmpty() )
{
startline++;
found = true;
}
else
found = findCurrentTexParagraph(startline,endline,view);
// we are in an empty line or in the first line of a paragraph
if ( found )
{
// find the last line of the previous paragraph
int foundline = -1;
for ( int line=startline-1; line>=0; --line )
{
if ( ! doc->textLine(line).stripWhiteSpace().isEmpty() )
break;
foundline = line;
}
if ( foundline < 0 )
return;
// and finally the first line of this paragraph
int prevstartline = -1;
for ( int line=foundline-1; line>=0; --line )
{
if ( doc->textLine(line).stripWhiteSpace().isEmpty() )
break;
prevstartline = line;
}
if ( prevstartline >= 0 )
view->setCursorPositionReal((uint)prevstartline,0);
}
}
//////////////////// gotoLine ////////////////////
void EditorExtension::gotoLine(Kate::View *view)
{
view = determineView(view);
if ( view )
view->gotoLine();
}
//////////////////// one line of text////////////////////
void EditorExtension::selectLine(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
// get current position
uint row,col;
TQString word;
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
if ( doc->lineLength(row) > 0 )
{
doc->setSelection(row,0,row+1,0);
}
}
void EditorExtension::deleteEndOfLine(Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint row,col;
view->cursorPositionReal(&row,&col);
Kate::Document *doc = view->getDoc();
doc->clearSelection();
doc->removeText(row,col,row,doc->lineLength(row));
}
//////////////////// LaTeX command ////////////////////
void EditorExtension::selectWord(EditorExtension::SelectMode mode, Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
// get current position
uint row,col,col1,col2;
TQString word;
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
if ( getCurrentWord(doc,row,col,mode,word,col1,col2) )
{
doc->setSelection(row,col1,row,col2);
}
}
void EditorExtension::deleteWord(EditorExtension::SelectMode mode, Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
// get current position
uint row,col,col1,col2;
TQString word;
Kate::Document *doc = view->getDoc();
view->cursorPositionReal(&row,&col);
if ( getCurrentWord(doc,row,col,mode,word,col1,col2) )
{
doc->removeText(row,col1,row,col2);
}
}
void EditorExtension::nextBullet(Kate::View* view)
{
gotoBullet(false, view);
}
void EditorExtension::prevBullet(Kate::View* view)
{
gotoBullet(true, view);
}
void EditorExtension::insertBullet(Kate::View* view)
{
uint col, pos;
view = determineView(view);
if(!view)
{
return;
}
view->cursorPositionReal(&col, &pos);
view->getDoc()->insertText(col, pos, s_bullet);
}
void EditorExtension::completeWord()
{
complete()->editComplete(m_ki->viewManager()->currentTextView(), KileDocument::CodeCompletion::cmLatex);
}
void EditorExtension::completeEnvironment()
{
complete()->editComplete(m_ki->viewManager()->currentTextView(), KileDocument::CodeCompletion::cmEnvironment);
}
void EditorExtension::completeAbbreviation()
{
complete()->editComplete(m_ki->viewManager()->currentTextView(), KileDocument::CodeCompletion::cmAbbreviation);
}
//////////////////// double quotes ////////////////////
void EditorExtension::initDoubleQuotes()
{
m_dblQuotes = KileConfig::insertDoubleQuotes();
int index = KileConfig::doubleQuotes();
if ( index<0 && index>=(int)m_quoteList.count() )
index = 0;
TQStringList quotes = TQStringList::split(TQRegExp("\\s{2,}"), m_quoteList[index] );
m_leftDblQuote= quotes[1];
m_rightDblQuote = quotes[2];
KILE_DEBUG() << "new quotes: " << m_dblQuotes << " left=" << m_leftDblQuote << " right=" << m_rightDblQuote<< endl;
}
bool EditorExtension::insertDoubleQuotes()
{
// don't insert double quotes, if konsole has focus
// return false, because if this is called from an event
// handler, because this event has to be passed on
if ( m_ki->texKonsole()->hasFocus() )
return false;
// insert double quotes, normal mode or autocompletion mode
// always return true for event handler
Kate::View *view = determineView(0L);
if ( !view ) return true;
Kate::Document *doc = view->getDoc();
if( doc && m_ki->extensions()->isTexFile(doc->url()) )
doc->removeSelectedText();
else
return false;
uint row,col;
view->cursorPositionReal(&row,&col);
// simply insert, if we are inside a verb command
if ( insideVerb(view) || insideVerbatim(view) )
return false;
// simply insert, if autoinsert mode is not active or the char bevor is \ (typically for \"a useful)
if ( !m_dblQuotes || ( col > 0 && doc->text(row,col-1,row,col) == TQString("\\") ) )
return false;
// insert with auto mode
KTextEditor::SearchInterface *iface;
iface = dynamic_cast<KTextEditor::SearchInterface *>(doc);
TQString pattern1 = TQRegExp::escape(m_leftDblQuote);
if ( m_leftDblQuote.at(m_leftDblQuote.length()-1).isLetter() )
pattern1 += "(\\b|(\\{\\}))";
TQString pattern2 = TQRegExp::escape(m_rightDblQuote);
if ( m_rightDblQuote.at(m_rightDblQuote.length()-1).isLetter() )
pattern2 += "(\\b|(\\{\\}))";
TQRegExp reg('(' + pattern1 + ")|(" + pattern2 + ')');
uint r,c,l;
bool openfound = false;
if ( iface->searchText(row,col,reg,&r,&c,&l,true) )
{
openfound = ( doc->textLine(r).find(m_leftDblQuote,c) == (int)c );
//KILE_DEBUG() << "pattern=" << reg.pattern() << " " << reg.cap(1) << " r=" << r << " c=" << c << " open=" << openfound<< endl;
}
TQString textline = doc->textLine(row);
//KILE_DEBUG() << "text=" << textline << " open=" << openfound << endl;
if ( openfound )
{
// If we last inserted a language specific doublequote open,
// we have to change it to a normal doublequote. If not we
// insert a language specific doublequote close
int startcol = col - m_leftDblQuote.length();
//KILE_DEBUG() << "startcol=" << startcol << " col=" << col << endl;
if ( startcol>=0 && textline.find(m_leftDblQuote,startcol) == (int)startcol )
{
doc->removeText(row,startcol,row,startcol+m_leftDblQuote.length());
doc->insertText(row,startcol,"\"");
}
else
{
doc->insertText(row,col,m_rightDblQuote);
}
}
else
{
// If we last inserted a language specific doublequote close,
// we have to change it to a normal doublequote. If not we
// insert a language specific doublequote open
int startcol = col - m_rightDblQuote.length();
//KILE_DEBUG() << "startcol=" << startcol << " col=" << col << endl;
if ( startcol>=0 && textline.find(m_rightDblQuote,startcol) == (int)startcol )
{
doc->removeText(row,startcol,row,startcol+m_rightDblQuote.length());
doc->insertText(row,startcol,"\"");
}
else
{
doc->insertText(row,col,m_leftDblQuote);
}
}
return true;
}
//////////////////// insert tabulator ////////////////////
void EditorExtension::insertIntelligentTabulator()
{
Kate::View *view = determineView(0L);
if ( !view ) return;
uint row,col,currentRow,currentCol;
TQString envname,tab;
TQString prefix = " ";
view->cursorPositionReal(&currentRow,&currentCol);
if ( findOpenedEnvironment(row,col,envname,view) )
{
// look if this is an environment with tabs
tab = m_latexCommands->getTabulator(envname);
// try to align tabulator with textline above
if ( currentRow >= 1 )
{
int tabpos = view->getDoc()->textLine(currentRow-1).find('&',currentCol);
if ( tabpos >= 0 )
{
currentCol = tabpos;
prefix = TQString();
}
}
}
if ( tab == TQString() )
tab = "&";
tab = prefix + tab + ' ';
view->getDoc()->insertText(currentRow,currentCol,tab);
view->setCursorPositionReal(currentRow,currentCol+tab.length());
}
//////////////////// autocomplete environment ////////////////////
// should we complete the current environment (call from KileEventFilter)
bool EditorExtension::eventInsertEnvironment(Kate::View *view)
{
// don't complete environment, if we are
// still working inside the completion box
if ( m_complete->inProgress() )
return false;
int row = view->cursorLine();
int col = view->cursorColumnReal();
TQString line = view->getDoc()->textLine(row).left(col);
int pos = m_regexpEnter.search(line);
if (pos != -1 )
{
line = m_regexpEnter.cap(1);
for (uint i=0; i < line.length(); ++i)
if ( ! line[i].isSpace() ) line[i] = ' ';
TQString envname, endenv;
if ( m_regexpEnter.cap(2) == "\\[" )
{
envname = m_regexpEnter.cap(2);
endenv = "\\]\n";
}
else
{
envname = m_regexpEnter.cap(4);
endenv = m_regexpEnter.cap(2).replace("\\begin","\\end") + '\n';
}
if ( shouldCompleteEnv(envname, view) )
{
TQString item = m_latexCommands->isListEnv(envname) ? "\\item " : TQString();
view->getDoc()->insertText(row,col, '\n'+line+m_envAutoIndent+item +'\n'+line+endenv);
view->setCursorPositionReal(row+1, line.length()+m_envAutoIndent.length()+item.length());
return true;
}
}
return false;
}
bool EditorExtension::shouldCompleteEnv(const TQString &env, Kate::View *view)
{
KILE_DEBUG() << "===EditorExtension::shouldCompleteEnv( " << env << " )===" << endl;
TQRegExp reTestBegin,reTestEnd;
if ( env == "\\[" )
{
KILE_DEBUG() << "display style" << endl;
reTestBegin.setPattern("(?:[^\\\\]|^)\\\\\\[");
// the first part is a non-capturing bracket (?:...) and we check if we don't have a backslash in front,
// or that we are at the begin of the line
reTestEnd.setPattern("(?:[^\\\\]|^)\\\\\\]");
}
else
{
reTestBegin.setPattern("(?:[^\\\\]|^)\\\\begin\\s*\\{" + TQRegExp::escape(env) + "\\}");
reTestEnd.setPattern("(?:[^\\\\]|^)\\\\end\\s*\\{" + TQRegExp::escape(env) + "\\}");
}
int num = view->getDoc()->numLines();
int numBeginsFound = 0;
int numEndsFound = 0;
uint realLine, realColumn;
view->cursorPositionReal(&realLine, &realColumn);
for ( int i = realLine; i < num; ++i)
{
numBeginsFound += view->getDoc()->textLine(i).contains(reTestBegin);
numEndsFound += view->getDoc()->textLine(i).contains(reTestEnd);
KILE_DEBUG() << "line is " << i << " numBeginsFound = " << numBeginsFound << " , " << "numEndsFound = " << numEndsFound << endl;
if ( numEndsFound >= numBeginsFound )
return false;
else if ( numEndsFound == 0 && numBeginsFound > 1 )
return true;
else if ( numBeginsFound > 2 || numEndsFound > 1 )
return true; // terminate the search
}
return true;
}
TQString EditorExtension::getWhiteSpace(const TQString &s)
{
TQString whitespace = s;
for ( uint i=0; i<whitespace.length(); ++i )
{
if ( ! whitespace[i].isSpace() )
whitespace[i] = ' ';
}
return whitespace;
}
//////////////////// inside verbatim commands ////////////////////
bool EditorExtension::insideVerbatim(Kate::View *view)
{
uint rowEnv,colEnv;
TQString nameEnv;
if ( findOpenedEnvironment(rowEnv,colEnv,nameEnv,view) )
{
if ( m_latexCommands->isVerbatimEnv(nameEnv) )
return true;
}
return false;
}
bool EditorExtension::insideVerb(Kate::View *view)
{
view = determineView(view);
if ( !view ) return false;
// get current position
uint row,col;
view->cursorPositionReal(&row,&col);
int startpos = 0;
TQString textline = getTextLineReal(view->getDoc(),row);
TQRegExp reg("\\\\verb(\\*?)(.)");
while ( true )
{
int pos = textline.find(reg,startpos);
if ( pos<0 || col<(uint)pos+6+reg.cap(1).length() )
return false;
pos = textline.find(reg.cap(2),pos+6+reg.cap(1).length());
if ( pos<0 || col<=(uint)pos )
return true;
startpos = pos + 1;
}
}
//////////////////// goto sectioning command ////////////////////
void EditorExtension::gotoNextSectioning()
{
gotoSectioning(false);
}
void EditorExtension::gotoPrevSectioning()
{
gotoSectioning(true);
}
void EditorExtension::gotoSectioning(bool backwards, Kate::View *view)
{
view = determineView(view);
if ( !view ) return;
uint rowFound,colFound;
m_ki->viewManager()->updateStructure(true);
if ( m_ki->structureWidget()->findSectioning(NULL, view->getDoc(), view->cursorLine(), view->cursorColumn(),
backwards, false, rowFound, colFound))
{
view->setCursorPositionReal(rowFound,colFound);
}
}
//////////////////// sectioning popup ////////////////////
void EditorExtension::sectioningCommand(KileListViewItem *item, int id)
{
Kate::View *view = determineView(0L);
if ( !view ) return;
if ( ! item )
return;
Kate::Document *doc = view->getDoc();
// try to determine the whole secting
// get the start auf the selected sectioning
uint row,col,row1,col1,row2,col2;
row = row1 = item->startline() - 1;
col = col1 = item->startcol() - 1;
// FIXME tbraun make this more clever, introdoce in kiledocinfo a flag which can be easily queried for that, so that we don't have to hardcode the names of the sections here (which is definitly a bad idea)
// check, if the document was changed in the meantime
TQRegExp reg( "\\\\(part|chapter|section|subsection|subsubsection|paragraph|subparagraph)\\*?\\s*(\\{|\\[)" );
TQString textline = getTextLineReal(doc,row1);
if ( reg.search(textline,col1) != (int)col1 )
{
m_ki->logWidget()->clear();
m_ki->logWidget()->printMsg(KileTool::Error,
i18n("The document was modified and the structure view should be updated, before starting such an operation."),
i18n("Structure View Error") );
return;
}
// increase cursor position and find the following sectioning command
if ( ! increaseCursorPosition(doc,row,col) )
return;
if (!m_ki->structureWidget()->findSectioning(item, doc, row, col, false, true, row2, col2))
{
// or the end of the document
// if there is a '\end{document} command, we have to exclude it
if ( ! findEndOfDocument(doc,row,col,row2,col2) )
{
row2 = doc->numLines() - 1;
col2 = 0;
}
}
// clear selection and make cursor position visible
doc->clearSelection();
view->setCursorPositionReal(row1,col1);
TQString text;
KTextEditor::EditInterfaceExt *iface = KTextEditor::editInterfaceExt( doc );
if ( iface ) iface->editBegin();
switch ( id )
{
case KileWidget::Structure::SectioningCut:
TQApplication::clipboard()->setText( doc->text(row1,col1,row2,col2) ); // copy -> clipboard
doc->removeText(row1,col1,row2,col2); // delete
break;
case KileWidget::Structure::SectioningCopy:
TQApplication::clipboard()->setText( doc->text(row1,col1,row2,col2) ); // copy -> clipboard
break;
case KileWidget::Structure::SectioningPaste:
text = TQApplication::clipboard()->text(); // clipboard -> text
if ( ! text.isEmpty() )
{
view->setCursorPositionReal(row2,col2); // insert
view->insertText(text + '\n');
}
break;
case KileWidget::Structure::SectioningSelect:
doc->setSelection(row1,col1,row2,col2); // select
break;
case KileWidget::Structure::SectioningDelete:
doc->removeText(row1,col1,row2,col2); // delete
break;
case KileWidget::Structure::SectioningComment:
doc->setSelection(row1,col1,row2,col2);
view->comment();
doc->clearSelection();
break;
case KileWidget::Structure::SectioningPreview:
doc->setSelection(row1,col1,row2,col2); // quick preview
m_ki->quickPreview()->previewSelection(doc,false);
doc->clearSelection();
break;
}
if ( iface ) iface->editEnd();
// update structure view, because it has changed
if ( id==KileWidget::Structure::SectioningDelete || id==KileWidget::Structure::SectioningComment )
m_ki->viewManager()->updateStructure(true);
}
bool EditorExtension::findEndOfDocument(Kate::Document *doc, uint row, uint col, uint &rowFound, uint &colFound)
{
KTextEditor::SearchInterface *iface;
iface = dynamic_cast<KTextEditor::SearchInterface *>(doc);
uint lenFound;
TQString textline;
while ( iface->searchText(row,col,"\\end{document}",&rowFound,&colFound,&lenFound) )
{
textline = getTextLineReal(doc,rowFound);
if ( textline.find("\\end{document}",colFound) == (int)colFound )
return true;
row = rowFound;
col = colFound;
if ( ! increaseCursorPosition(doc,row,col) )
break;
}
return false;
}
}
#include "kileedit.moc"