NCL Composer  0.1.5
 All Classes Functions Variables Pages
QsciNCLAPIs.cpp
1 /*
2  * Copyright 2011 TeleMidia/PUC-Rio.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see
16  * <http://www.gnu.org/licenses/>.
17  */
18 #include "QsciNCLAPIs.h"
19 
20 #include "NCLTextEditor.h"
21 
22 #include <QFileDialog>
23 
24 #include <core/util/Utilities.h>
25 using namespace composer::core::util;
26 
27 QsciNCLAPIs::QsciNCLAPIs(QsciLexer * lexer) :
28  QsciAPIs(lexer)
29 {
30  nclStructure = NCLStructure::getInstance();
31 
32  add (QString("ncl30"));
33  prepare();
34 }
35 
36 QsciNCLAPIs::~QsciNCLAPIs()
37 {
38 
39 }
40 
41 void QsciNCLAPIs::updateAutoCompletionList( const QStringList &context,
42  QStringList &list)
43 {
44  suggesting = SUGGESTING_OTHER;
45 
46  int pos = lexer()->editor()
47  ->SendScintilla(QsciScintilla::SCI_GETSELECTIONSTART);
48  qDebug() << "updateAutoCompletionList to";
49 
50  if ( isElement(pos) )
51  {
52  suggesting = SUGGESTING_ELEMENTS;
53  qDebug() << "Must suggest elements" << endl;
54  for (int i = 0; i < context.count(); ++i) {
55  qDebug() << "'" << context[i] << "'";
56  QString father = getParentTagName(pos);
57  qDebug() << "Father = " << father;
58  if(father == "")
59  father = "NULL";
60 
61  map <QString, char> *elements = nclStructure->getChildren(father);
62  map <QString, char>::iterator it;
63 
64  if(elements != NULL)
65  {
66  it = elements->begin();
67  for (; it != elements->end(); ++it)
68  {
69  if(it->first.startsWith(context[i]))
70  {
71  QString str(it->first);
72  list.push_back(str);
73  qDebug() << it->first;
74  }
75  }
76  }
77  }
78  }
79  else if ( isAttribute(pos) )
80  {
81  suggesting = SUGGESTING_ATTRIBUTES;
82  QString tagname = getCurrentTagName(pos);
83  QStringList current_attrs;
84 
85  getAttributesTyped(pos, current_attrs);
86  for (int i = 0; i < current_attrs.size(); i++)
87  qDebug() << "Already typed: " << current_attrs.at(i);
88 
89  qDebug() << "Must suggest attributes to " << tagname;
90  if(tagname != "")
91  {
92  map <QString, bool> *attrs = nclStructure->getAttributes(tagname);
93  deque <QString> *attrs_ordered =
94  nclStructure->getAttributesOrdered(tagname);
95 
96  if(attrs != NULL){
97  deque <QString>::iterator it;
98  for (int i = 0; i < context.count(); ++i)
99  {
100  it = attrs_ordered->begin();
101 
102  for (; it != attrs_ordered->end(); ++it){
103  if( it->startsWith(context[i])
104  && !current_attrs.contains(*it))
105  {
106  QString str = *it;
107  list.push_back(str);
108  qDebug() << *it;
109  }
110  }
111  }
112  }
113  }
114  }
115  else if ( isAttributeValue(pos) )
116  {
117  suggesting = SUGGESTING_ATTRIBUTE_VALUES;
118  QString tagname = getCurrentTagName(pos);
119  QString attribute = getCurrentAttribute(pos);
120  QString datatype = nclStructure->getAttributeDatatype(tagname, attribute);
121  NCLTextEditor *nclEditor = ((NCLTextEditor *)lexer()->editor());
122 
123  qDebug() << tagname << ":" << attribute << " -> datatype=" << datatype;
124 
125  QStringList defaultSuggestion =
126  nclStructure->getDatatypeDefaultSuggestions(datatype);
127 
128  list.append(defaultSuggestion);
129 
130  vector <AttributeReferences *>
131  references = nclStructure->getReferences(tagname, attribute);
132 
133  if(datatype == "URI")
134  {
135  QString filename = QFileDialog::getOpenFileName(nclEditor,
136  tr("Select file"),
137  // nclEditor->getDocumentUrl()
138  Utilities::getLastFileDialogPath());
139 
140  if(!filename.isEmpty() && !filename.isNull())
141  {
142  Utilities::updateLastFileDialogPath(filename);
143 
144  try
145  {
146  filename = relativePath(nclEditor->getDocumentUrl(), filename, true);
147  }
148  catch(...)
149  {
150  }
151  nclEditor->removeSelectedText();
152  //Removes an empty space automatically inserted by fillingAttributes.
153  if(nclEditor->SendScintilla(QsciScintilla::SCI_GETCHARAT, pos) == ' ')
154  {
155  nclEditor->SendScintilla(QsciScintilla::SCI_GOTOPOS, pos+1);
156  nclEditor->SendScintilla(QsciScintilla::SCI_DELETEBACK);
157  }
158 
159  nclEditor->insert(filename);
160  }
161  }
162 
163  if(references.size()) //the attribute should be a reference to other
164  //attribute
165  {
166 // NCLTextEditor *nclEditor = ((NCLTextEditor *)lexer()->editor());
167  if(nclEditor->parseDocument()) //parse our current document (and the
168  //included ones).
169  {
170  for(unsigned int i = 0; i < references.size(); i++)
171  {
172  qDebug() << "Should refer to " << references[i]->getRefElement()
173  << "." << references[i]->getRefAttribute()
174  << "in the scope: " << references[i]->getScope();
175 
176  QList <QDomElement> elements;
177 
178  // If we have an ANY_SCOPE, it doesn't need any special treatment.
179  if(references[i]->getScope() == AttributeReferences::ANY_SCOPE)
180  {
181  elements = nclEditor->
182  elementsByTagname(references[i]->getRefElement());
183  }
184  else if(references[i]->getScope() == AttributeReferences::SAME_SCOPE)
185  {
186  int parent_offset = getParentOffset(pos);
187  while(!nclStructure->defineScope(getCurrentTagName(parent_offset)))
188  {
189  parent_offset = getParentOffset(parent_offset);
190  }
191 
192  QString idValue =
193  getAttributeValueFromCurrentElement(parent_offset, "id");
194 
195  // \todo suggest elements child of an element with no id.
196  elements = nclEditor->
197  elementsByTagname( references[i]->getRefElement(),
198  idValue);
199  }
200  else if(references[i]->getScope() ==
201  AttributeReferences::USERDEFINED_SCOPE)
202  {
203  QString attr;
204  QString userDefinedScope = references[i]->getUserDefinedScope();
205 
206  qDebug() << "User defined scope" << userDefinedScope;
207  // \todo The user defined must be recursive, i.e., the user could
208  // define $PARENT.$PARENT.$PARENT...
209  if(userDefinedScope.startsWith("$THIS"))
210  {
211  attr = userDefinedScope.mid(6);
212  qDebug() << "$THIS" << attr;
213  QString idValue = getAttributeValueFromCurrentElement(pos, attr);
214  elements = nclEditor->elementsByTagname(
215  references[i]->getRefElement(), idValue);
216  }
217  else if(userDefinedScope.startsWith("$PARENT"))
218  {
219  attr = userDefinedScope.mid(8);
220  qDebug() << "$PARENT" << attr;
221  int parent_offset = getParentOffset(pos);
222  qDebug() << "parent_offset" << parent_offset;
223  QString idValue = getAttributeValueFromCurrentElement(
224  parent_offset, attr);
225 
226  qDebug() << "idValue = " << idValue;
227  elements = nclEditor->elementsByTagname(
228  references[i]->getRefElement(), idValue);
229 
230  }
231  else if(userDefinedScope.startsWith("$GRANDPARENT"))
232  {
233  attr = userDefinedScope.mid(13);
234  qDebug() << "$GRANDPARENT" << attr;
235  int grandparent_offset = getParentOffset(getParentOffset(pos));
236  QString idValue = getAttributeValueFromCurrentElement(
237  grandparent_offset, attr);
238  elements = nclEditor->elementsByTagname(
239  references[i]->getRefElement(), idValue);
240  }
241  }
242 
243  for(int j = 0; j < elements.length(); j++)
244  {
245  QDomElement node = elements.at(j).toElement();
246 
247  QString attributeValue =
248  node.attribute(references[i]->getRefAttribute());
249 
250  qDebug() << context.count();
251  for (int k = 0; k < context.count(); ++k)
252  {
253  if(attributeValue.startsWith(context[k]))
254  list.append(attributeValue);
255  }
256  }
257  }
258  }
259  }
260  else
261  {
262  qDebug() << "Coud not parse the document";
263  }
264  }
265  list.removeDuplicates();
266 }
267 
268 //TODO: END ELEMENT, ATTRIBUTE VALUE
269 void QsciNCLAPIs::autoCompletionSelected(const QString &selection)
270 {
271  int start = 0, line, pos;
272  QString outputStr("");
273  QString strline;
274  bool fixidentation = false;
275  bool hasAttributes = false;
276 
277  //PREPARING TO INSERT TEXT
278  lexer()->editor()->beginUndoAction();
279  //cancelling autocomplete (avoid scintilla insert the selection)
280  lexer()->editor()->SendScintilla(QsciScintilla::SCI_AUTOCCANCEL);
281  lexer()->editor()->getCursorPosition(&line, &pos);
282  //delete original word from text
283  start = pos - 1;
284  strline = lexer()->editor()->text( line );
285 
286  //if the user already put a word, delete this word
287  // (autocomplete will put it entirely)
288  if(start >= 0 && strline.at(start).isLetter())
289  {
290  lexer()->editor()->SendScintilla( QsciScintilla::SCI_DELWORDLEFT );
291 
292  //update the start position and the content line
293  lexer()->editor()->getCursorPosition(&line, &pos);
294  strline = lexer()->editor()->text(line);
295  start = pos-1;
296  }
297 
298  if(suggesting == SUGGESTING_ELEMENTS)
299  {
300  QString attributes = getRequiredAttributesAsStr(selection);
301 
302  if(attributes != "")
303  hasAttributes = true;
304 
305  if(start < 0 || strline.at(start) != '<')
306  outputStr += "<";
307  outputStr += selection + " " + attributes;
308 
309  map <QString, char> *children = nclStructure->getChildren(selection);
310  if ( children != NULL && children->size() ) {
311  outputStr += ">";
312  outputStr += "\n";
313  outputStr += "</";
314  outputStr += selection;
315  outputStr += ">";
316  fixidentation = true;
317  }
318  else {
319  outputStr += "/>";
320  }
321 
322  }
323  else if(suggesting == SUGGESTING_ATTRIBUTES)
324  {
325  outputStr = selection + "=\"\"";
326 
327  //if the attribute is just after another word, put a space between then
328  if(start >= 0){
329  QChar ch = strline.at(start);
330  qDebug() << ch;
331  if(!ch.isSpace())
332  outputStr.prepend(' ');
333  }
334  }
335  else if(suggesting == SUGGESTING_ATTRIBUTE_VALUES)
336  {
337  int ps = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCURRENTPOS)
338  - 1;
339  int pe = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCURRENTPOS);
340  bool quote1_found = false, quote2_found = false;
341  char ch;
342 
343  int startline =
344  lexer()->editor()->SendScintilla(
345  QsciScintilla::SCI_GETLINESELSTARTPOSITION, ps);
346  int endline =
347  lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETLINEENDPOSITION,
348  pe);
349  //find quote
350  while(ps >= startline && !quote1_found){
351  ch = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCHARAT, ps);
352  if(ch == '\'' || ch == '\"')
353  quote1_found = true;
354  ps--;
355  }
356  //find quote
357  while(pe <= endline && !quote2_found){
358  ch = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCHARAT, pe);
359  if(ch == '\'' || ch == '\"')
360  quote2_found = true;
361  pe++;
362  }
363 
364  qDebug() << quote1_found << quote2_found;
365 
366  if(quote1_found && quote2_found)
367  {
368  lexer()->editor()->SendScintilla(QsciScintilla::SCI_SETSELECTIONSTART,
369  ps+2);
370  lexer()->editor()->SendScintilla(QsciScintilla::SCI_SETSELECTIONEND,
371  pe-1);
372  lexer()->editor()->removeSelectedText();
373  }
374 
375  outputStr = selection;
376  }
377 
378  // insert the new word (already managed)
379  pos = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCURRENTPOS);
380  lexer()->editor()->insert(outputStr);
381  // fix identation
382  if(fixidentation)
383  {
384  int lineident = lexer()->editor()->SendScintilla (
385  QsciScintilla::SCI_GETLINEINDENTATION,
386  line);
387 
388  lexer()->editor()->SendScintilla( QsciScintilla::SCI_SETLINEINDENTATION,
389  line+1,
390  lineident);
391  }
392 
393  // move cursor to the new position
394  lexer()->editor()->SendScintilla(QsciScintilla::SCI_WORDRIGHT);
395  lexer()->editor()->SendScintilla(QsciScintilla::SCI_WORDRIGHT);
396 
397  lexer()->editor()->endUndoAction();
398 
399  if (suggesting == SUGGESTING_ELEMENTS)
400  {
401  if(hasAttributes)
402  {
403  lexer()->editor()->recolor();
404  ((NCLTextEditor *)lexer()->editor())->userFillingNextAttribute(pos);
405  }
406  }
407 }
408 
409 
410 QStringList QsciNCLAPIs::callTips(const QStringList &context, int commas,
411  QsciScintilla::CallTipsStyle style,
412  QList<int> &shifts)
413 {
414  (void) context; (void) commas; (void) style; (void) shifts;
415 
416  QStringList list;
417  QString str("TODO");
418  list.push_back(str);
419 
420  return list;
421 }
422 
423 bool QsciNCLAPIs::event(QEvent *e)
424 {
425  (void) e;
426  // qDebug() << "QsciNCLAPIs::event" << e;
427  return true;
428 }
429 
430 QString QsciNCLAPIs::getRequiredAttributesAsStr(const QString &element)
431 {
432  QString ret("");
433  map <QString, bool> *attributes = NCLStructure::getInstance()
434  ->getAttributes(element);
435  deque <QString> *attrs_ordered = NCLStructure::getInstance()
436  ->getAttributesOrdered(element);
437 
438  if(attributes != NULL && attrs_ordered != NULL) {
439  deque <QString>::iterator it;
440  bool first = true;
441  for(it = attrs_ordered->begin(); it != attrs_ordered->end(); ++it){
442  QString str = *it;
443  if(attributes->count(str) && (*attributes)[str]) { //if it is required
444  if(!first)
445  ret += " ";
446  first = false;
447  ret += *it + "=\"\"";
448  }
449  }
450  }
451  return ret;
452 }
453 
454 bool QsciNCLAPIs::isElement(int pos)
455 {
456  int style = lexer()->editor()
457  ->SendScintilla( QsciScintilla:: SCI_GETSTYLEAT, pos);
458  qDebug() << "Style=" << style;
459  if(style == QsciLexerNCL::Default)
460  {
461  return true;
462  }
463  else if( style == QsciLexerNCL::Tag ||
464  style == QsciLexerNCL::XMLTagEnd ||
465  style == QsciLexerNCL::XMLStart ||
466  style == QsciLexerNCL::OtherInTag ||
467  style == QsciLexerNCL::UnknownTag)
468  {
469  int p = pos-1;
470  while(p >= 0)
471  {
472  char ch = lexer()->editor()
473  ->SendScintilla( QsciScintilla::SCI_GETCHARAT, p);
474  char previous_ch = lexer()->editor()
475  ->SendScintilla( QsciScintilla::SCI_GETCHARAT, p-1);
476  if(isspace(ch))
477  break;
478  if(ch == '<')
479  {
480  if(previous_ch == '/')
481  return false; // \todo should return a end_tag
482  else
483  return true;
484  }
485  p--;
486  }
487  }
488  return false;
489 }
490 
491 //TODO: returning false when just before >
492 bool QsciNCLAPIs::isAttribute(int pos)
493 {
494  qDebug() << "QsciNCLAPIs::isAttribute";
495  int style = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETSTYLEAT,
496  pos);
497  if ( style == QsciLexerNCL::Attribute )
498  return true;
499  else if( style == QsciLexerNCL::Tag ||
500  style == QsciLexerNCL::XMLTagEnd ||
501  style == QsciLexerNCL::XMLStart ||
502  style == QsciLexerNCL::OtherInTag ||
503  style == QsciLexerNCL::UnknownTag){
504  int p = pos-1;
505  while(p >= 0){
506  char ch = lexer()->editor()
507  ->SendScintilla (
508  QsciScintilla::SCI_GETCHARAT,
509  p
510  );
511  if(isspace(ch)) return true;
512  if(ch == '<') return true;
513  p--;
514  }
515  }
516  return false;
517 }
518 
519 QString QsciNCLAPIs::getCurrentTagName(int pos)
520 {
521  int p = pos;
522  char ch = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCHARAT, p);
523 
524  if(isElement(pos)){
525  while (p >= 0 && ch != '<'){
526  p--;
527  ch = lexer()->editor()->SendScintilla( QsciScintilla::SCI_GETCHARAT,
528  p);
529  }
530 
531  if(p >= 0){
532  p++;
533  ch = lexer()->editor()->SendScintilla( QsciScintilla::SCI_GETCHARAT,
534  p);
535  QString word("");
536  while(isalnum(ch)){
537  word += ch;
538  p++;
539  ch = lexer()->editor()
540  ->SendScintilla( QsciScintilla::SCI_GETCHARAT,
541  p);
542  }
543  return word;
544  }
545  }
546  else {
547  while(p >= 0){
548  QString word("");
549  ch = lexer()->editor()->SendScintilla( QsciScintilla::SCI_GETCHARAT,
550  p);
551  while(p >= 0 && isalnum(ch)){
552  word.prepend(ch);
553  p--;
554  ch = lexer()->editor()
555  ->SendScintilla( QsciScintilla::SCI_GETCHARAT,
556  p);
557  }
558  if(ch == '<') return word;
559  p--;
560  }
561 
562  }
563  return QString("");
564 }
565 
566 QString QsciNCLAPIs::getCurrentAttribute (int pos)
567 {
568  int p = pos;
569  char ch;
570  QString current_attribute = "";
571  if (isAttributeValue(pos))
572  {
573  bool quote_found = false, equal_found = false;
574  //find quote
575  while(p >= 0 && !quote_found){
576  ch = lexer()->editor()->SendScintilla( QsciScintilla::SCI_GETCHARAT,
577  p);
578  if(ch == '\'' || ch == '\"')
579  quote_found = true;
580  p--;
581  }
582  //find equal
583  while(p >= 0 && !equal_found){
584  ch = lexer()->editor()->SendScintilla( QsciScintilla::SCI_GETCHARAT,
585  p);
586  if(ch == '=')
587  equal_found = true;
588  p--;
589  }
590  //remove any whitespace between = and attribute name
591  while (p >= 0) {
592  ch = lexer()->editor()->SendScintilla( QsciScintilla::SCI_GETCHARAT,
593  p);
594  if(!isspace(ch))
595  break;
596  p--;
597  }
598  //get the attribute name
599  while(p >= 0){
600  ch = lexer()->editor()->SendScintilla( QsciScintilla::SCI_GETCHARAT,
601  p);
602  if(!isalnum(ch))
603  break;
604  current_attribute.prepend(ch);
605  p--;
606  }
607 
608  current_attribute = current_attribute.trimmed();
609  }
610 
611  return current_attribute;
612 }
613 
614 //TODO: Eliminate closed tags in the way
615 int QsciNCLAPIs::getParentOffset(int pos)
616 {
617  int p = pos;
618  char ch = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCHARAT, p);
619 
620  int reading = -1;
621  int closed_tags = 0;
622  while (p >= 0)
623  {
624  p--;
625  ch = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCHARAT, p);
626  if(ch == '>') {
627  reading = QsciLexerNCL::XMLStart;
628  p--;
629  if (p < 0) break;
630  ch = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCHARAT, p);
631  if ( ch == '/')
632  reading = QsciLexerNCL::XMLTagEnd;
633  else if (ch == '-')
634  reading = QsciLexerNCL::HTMLComment;
635  else {
636  char lastch = ch;
637  while (p >= 0 && ch != '<'){
638  p--;
639  lastch = ch;
640  ch = lexer()->editor()
641  ->SendScintilla(QsciScintilla::SCI_GETCHARAT, p);
642  }
643  if(lastch == '/') reading = QsciLexerNCL::XMLEnd;
644 
645  if(reading == QsciLexerNCL::XMLEnd)
646  closed_tags ++;
647  else if(reading == QsciLexerNCL::XMLStart)
648  closed_tags --;
649 
650  qDebug() << "closed_tags = " << closed_tags << " p=" << p;
651  if(closed_tags < 0)
652  return p+2;
653  }
654  }
655  }
656  return -1;
657 }
658 
659 QString QsciNCLAPIs::getAttributeValueFromCurrentElement(int pos, QString attr)
660 {
661  qDebug() << "QsciNCLAPIs::getAttributeValueFromCurrentElement";
662  int start = getStartTagBegin(pos);
663  int length = getStartTagLength(pos);
664  int end = start + length;
665  QString attrValue = "";
666 
667  if(start < 0 || end < 0) return "";
668 
669  QString text;
670  char *chars = (char *) malloc ((end - start) * sizeof(char) + 1);
671  lexer()->editor()->SendScintilla( QsciScintilla::SCI_GETTEXTRANGE,
672  start,
673  end,
674  chars);
675  text = QString(chars);
676  qDebug() << "text = " << text;
677 
678 
679  QDomDocument domDoc;
680  QString tagname = getCurrentTagName(pos);
681  // \fixme I am assuming that the text is not closing the tag. This will
682  // probably cause some problem.
683  text += "</";
684  text += tagname;
685  text += ">";
686  if(domDoc.setContent(text))
687  {
688  attrValue = domDoc.firstChildElement().attribute(attr);
689  }
690  delete chars;
691  return attrValue;
692 }
693 
694 QString QsciNCLAPIs::getParentTagName(int pos)
695 {
696  int p_offset = getParentOffset(pos);
697  if(p_offset > 0)
698  return getCurrentTagName(p_offset);
699  return "";
700 }
701 
702 //FIXME: By now, it will consider any quoted string as attribute value, but
703 // this is not true.
704 bool QsciNCLAPIs::isAttributeValue(int pos)
705 {
706  qDebug() << "QsciNCLAPIs::isAttributeValue";
707  int style = lexer()->editor()
708  ->SendScintilla( QsciScintilla:: SCI_GETSTYLEAT,
709  pos);
710 
711  if ( style == QsciLexerNCL::HTMLDoubleQuotedString
712  || style == QsciLexerNCL::HTMLSingleQuotedString )
713 
714  return true;
715 
716  return false;
717 }
718 
719 //FIXME: be sure we are in a start tag
720 int QsciNCLAPIs::getStartTagBegin(int pos)
721 {
722  int p = pos;
723  char ch = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCHARAT, p);
724  while (p >= 0 && ch != '<'){
725  p--;
726  ch = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCHARAT, p);
727  }
728  return p;
729 }
730 
731 //FIXME: Be sure that we are in a start tag
732 int QsciNCLAPIs::getStartTagLength(int pos)
733 {
734  int start = getStartTagBegin(pos);
735  int p = pos;
736  int text_length = lexer()->editor()
737  ->SendScintilla(QsciScintilla::SCI_GETLENGTH);
738  char ch = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCHARAT, p);
739 
740  while (p < text_length && ch != '>'){
741  p++;
742  ch = lexer()->editor()->SendScintilla(QsciScintilla::SCI_GETCHARAT, p);
743  }
744  return p >= text_length ? -1 : (p-start+1);
745 }
746 
747 void QsciNCLAPIs::getAttributesTyped(int pos, QStringList &attrs)
748 {
749  int start = getStartTagBegin(pos);
750  int length = getStartTagLength(pos);
751  int end = start + length;
752 
753  qDebug() << "start=" << start << " end=" << end;
754 
755  if(start < 0 || end < 0) return;
756 
757  QString text;
758  char *chars = (char *) malloc ((end - start) * sizeof(char) + 1);
759  lexer()->editor()->SendScintilla( QsciScintilla::SCI_GETTEXTRANGE,
760  start,
761  end,
762  chars);
763  text = QString(chars);
764  qDebug() << "text = " << text;
765 
766  //FIXME: The following regex is not completely correct. Te text inside
767  // the attribute will be matched in some cases.
768  QRegExp attrRegex ("\\s[a-zA-Z]+");
769 
770  int lastIndex = 0;
771  int index = attrRegex.indexIn(text, lastIndex);
772  while(index != -1){
773  if (index + attrRegex.matchedLength() > end)
774  break;
775  qDebug() << index << " " << attrRegex.matchedLength();
776  attrs << text.mid(index, attrRegex.matchedLength()).trimmed();
777 
778  lastIndex = (index + attrRegex.matchedLength());
779  index = attrRegex.indexIn(chars, lastIndex);
780  }
781  delete chars;
782 }
783 
784 QString QsciNCLAPIs::relativePath( QString absolutePath, QString relativeTo,
785  bool bIsFile /*= false*/ )
786 {
787  QStringList absoluteDirectories = absolutePath.split( '/', QString::SkipEmptyParts );
788  QStringList relativeDirectories = relativeTo.split( '/', QString::SkipEmptyParts );
789 
790  //Get the shortest of the two paths
791  int length = absoluteDirectories.count() < relativeDirectories.count() ? absoluteDirectories.count() : relativeDirectories.count();
792 
793  //Use to determine where in the loop we exited
794  int lastCommonRoot = -1;
795  int index;
796 
797  //Find common root
798  for (index = 0; index < length; index++)
799  if (absoluteDirectories[index] == relativeDirectories[index])
800  lastCommonRoot = index;
801  else
802  break;
803 
804  //If we didn't find a common prefix then throw
805  if (lastCommonRoot == -1)
806  throw QString("Paths do not have a common base");
807 
808  //Build up the relative path
809  QString relativePath;
810 
811  //Add on the ..
812  for (index = lastCommonRoot + 1; index < absoluteDirectories.count() - (bIsFile?1:0); index++)
813  if (absoluteDirectories[index].length() > 0)
814  relativePath.append("../");
815 
816  //Add on the folders
817  for (index = lastCommonRoot + 1; index < relativeDirectories.count() - 1; index++)
818  relativePath.append( relativeDirectories[index] ).append( "/" );
819  relativePath.append(relativeDirectories[relativeDirectories.count() - 1]);
820 
821  return relativePath;
822 }