Index: addons/symbolviewer/CMakeLists.txt =================================================================== --- addons/symbolviewer/CMakeLists.txt +++ addons/symbolviewer/CMakeLists.txt @@ -1,7 +1,19 @@ add_definitions(-DTRANSLATION_DOMAIN=\"katesymbolviewer\") ########### next target ############### -set(katesymbolviewerplugin_PART_SRCS cpp_parser.cpp tcl_parser.cpp fortran_parser.cpp perl_parser.cpp -php_parser.cpp xslt_parser.cpp ruby_parser.cpp python_parser.cpp bash_parser.cpp ecma_parser.cpp plugin_katesymbolviewer.cpp ) +set(katesymbolviewerplugin_PART_SRCS + plugin_katesymbolviewer.cpp + bash_parser.cpp + cpp_parser.cpp + ecma_parser.cpp + fortran_parser.cpp + perl_parser.cpp + php_parser.cpp + plaintext_parser.cpp + python_parser.cpp + ruby_parser.cpp + tcl_parser.cpp + xslt_parser.cpp +) # resource for ui file and stuff qt5_add_resources(katesymbolviewerplugin_PART_SRCS plugin.qrc) Index: addons/symbolviewer/plaintext_parser.cpp =================================================================== --- /dev/null +++ addons/symbolviewer/plaintext_parser.cpp @@ -0,0 +1,289 @@ +/*************************************************************************** + plaintext_parser.cpp - description + ------------------- + begin : Jul 2018 + author : loh.tar + email : loh.tar@googlemail.com + ***************************************************************************/ +/*************************************************************************** + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +***************************************************************************/ + +#include + +#include "plugin_katesymbolviewer.h" + +namespace Plain_Text_Parser_Private { +// Because there is no "ParserClass", adding a special member function only for +// parser_foo looks very ugly, we do it in some less ugly way which requires +// still ugly global variables and functions +enum NodeType { + RootNode, + SectNode, + HeadNode, + ParaNode +}; + +KatePluginSymbolViewer *g_plugin(nullptr); +QTreeWidget *g_symbols(nullptr); +QTreeWidgetItem *g_lastNode(nullptr); +int g_paraLineNumber; // Where the current paragraph begins +bool g_structIsOff; +bool g_funcIsOff; +bool g_macroIsOff; + +void g_addNode(int nodeType, const QString &text, int lineNumber) +{ + QTreeWidgetItem *node(nullptr); + + // Indicate, there is no paragraph waiting for completion + g_paraLineNumber = -1; + + switch (nodeType) { + case SectNode: + if (g_structIsOff) return; + break; + case HeadNode: + if (g_funcIsOff) return; + break; + case ParaNode: + if (g_macroIsOff) return; + break; + default: + break; + }; + + if (g_plugin->treeOn) { + switch (nodeType) { + case RootNode: + node = new QTreeWidgetItem(g_symbols, nodeType); + g_symbols->setRootIsDecorated(1); + break; + default: + while (g_lastNode->type() >= nodeType) { + g_lastNode = g_lastNode->parent(); + } + node = new QTreeWidgetItem(g_lastNode, nodeType); + break; + }; + g_lastNode = node; + + } else { + switch (nodeType) { + case RootNode: + g_symbols->setRootIsDecorated(1); + break; + default: + node = new QTreeWidgetItem(g_symbols, nodeType); + break; + }; + } + + QIcon icon; + switch (nodeType) { + case RootNode: + icon = QIcon(QPixmap(const_cast(method_xpm))); + break; + case SectNode: + icon = QIcon(QPixmap(const_cast(class_xpm))); + break; + case HeadNode: + icon = QIcon(QPixmap(const_cast(struct_xpm))); + break; + case ParaNode: + icon = QIcon(QPixmap(const_cast(macro_xpm))); + break; + }; + + if (node) { + node->setText(0, text); + node->setIcon(0, icon); + node->setText(1, QString::number(lineNumber, 10)); + + if (g_plugin->expandedOn) { + g_symbols->expandItem(node->parent()); + } + } +} + +enum LineType { + EmptyLine, + NormalLine, + HeaderLine, + SectionLine +}; + +QQueue g_lineTypeHistory; +QQueue g_lineHistory; + +void g_initHistory() +{ + g_paraLineNumber = -1; + g_lineTypeHistory.clear(); + g_lineHistory.clear(); + g_lineTypeHistory.enqueue(EmptyLine); + g_lineHistory.enqueue(QLatin1String("")); + g_lineTypeHistory.enqueue(EmptyLine); + g_lineHistory.enqueue(QLatin1String("")); +} + +} // [END] namespace Plain_Text_Parser_Private + + +void KatePluginSymbolViewerView::parsePlainTextSymbols(void) +{ + if (!m_mainWindow->activeView()) + return; + + using namespace Plain_Text_Parser_Private; + + g_plugin = m_plugin; + g_symbols = m_symbols; + // Must copy each, m_struct is private, g_this->m_struct would not work + g_structIsOff = !m_struct->isChecked(); + g_funcIsOff = !m_func->isChecked(); + g_macroIsOff = !m_macro->isChecked(); + + // Rename menu actions + // FIXME Make them full configurable, not limited to 3 and not fix named + // (not the caption but the access name) need review of all parser + m_struct->setText(i18n("Show Sections")); + m_func->setText(i18n("Show Header")); + m_macro->setText(i18n("Show Paragraphs")); + + // We should also rename the header caption, but that is then not restored + // after a switch to an other document -> Needs a patch e.g. in parseSymbols() + //m_symbols->headerItem()->setText(0, i18n("Sections")); + + g_addNode(RootNode, i18n("Document"), 0); + + // Some flow/status control variables + QString currLine; // Current + QString paraLine; // First line of a paragraph + + g_initHistory(); + + KTextEditor::Document *kDoc = m_mainWindow->activeView()->document(); + + // Run the loop one line more as usually needed... + for (int i = 0; i < kDoc->lines() + 1; i++) { + + // ...to ensure a last paragraph is added properly... + if (i < kDoc->lines()) { + currLine = kDoc->line(i); + currLine = currLine.trimmed(); + currLine = currLine.simplified(); + currLine.truncate(80); // Limit the size to some acceptable length + } else { + if (g_paraLineNumber == -1) { + // Good, we are done! + break; + } + // ...when there was no \n at the last line + currLine.clear(); + } + + // Treat some lines as empty lines to avaoid to start a new paragraph + // or to break a continuation paragraph. Useful with some config files + if (currLine.size() == 1) { + currLine.clear(); + } else if (currLine.contains(QRegExp(QLatin1String("^[-#*./]{1,2}$")))) { + currLine.clear(); + } + + if (currLine.isEmpty() && g_lineTypeHistory.last() == EmptyLine) { + // Ignore consecutive empty lines + continue; + } + + // Let's start the investigation + bool currIsSection = currLine.contains(QRegExp(QLatin1String("^[=#*]{3,}$"))); + bool currIsHeader = currLine.contains(QRegExp(QLatin1String("^[-~^]{3,}$"))); + + // Keep a record of the history... + g_lineHistory.enqueue(currLine); + if (currLine.isEmpty()) { + g_lineTypeHistory.enqueue(EmptyLine); + } else if (currIsHeader) { + g_lineTypeHistory.enqueue(HeaderLine); + } else if (currIsSection) { + g_lineTypeHistory.enqueue(SectionLine); + } else { + g_lineTypeHistory.enqueue(NormalLine); + } + + // ...but not too much + if (g_lineHistory.size() > 3) { + g_lineHistory.dequeue(); + g_lineTypeHistory.dequeue(); + } + + // Waste some memory to increase readability + const LineType lineType0 = g_lineTypeHistory.at(0); // Oldest line + const LineType lineType1 = g_lineTypeHistory.at(1); + const LineType lineType2 = g_lineTypeHistory.at(2); // Current line + + // Check for Paragraph begin + if (g_paraLineNumber < 0) { + if (lineType1 == NormalLine) { + paraLine = g_lineHistory.at(1); + g_paraLineNumber = i - 1; + } else if (lineType2 == NormalLine) { + paraLine = g_lineHistory.at(2); + g_paraLineNumber = i; + } else { + continue; + } + } + + // Special checks for ISO date header. They to add in a sane way and give them + // an own LineType proved to be surprisingly complicated, so it's done quirky. + // Thanks to https://stackoverflow.com/a/46362201 + QRegExp isoDate(QLatin1String("^\\d{4}-([0]\\d|1[0-2])-([0-2]\\d|3[01])$")); + bool line0IsDate = g_lineHistory.at(0).contains(isoDate); + bool line1IsDate = g_lineHistory.at(1).contains(isoDate); + + if (line0IsDate && lineType1 == SectionLine && lineType2 == NormalLine) { + QString mask(QLatin1String("%1 %2")); + g_lastNode->setText(0, mask.arg(g_lineHistory.at(0)).arg(g_lineHistory.at(2))); + g_initHistory(); + + } else if (lineType0 != NormalLine && line1IsDate && lineType2 == NormalLine) { + QString mask(QLatin1String("%1 %2")); + g_addNode(SectNode, mask.arg(g_lineHistory.at(1)).arg(g_lineHistory.at(2)), i - 1); + g_initHistory(); + // [End] Special checks for ISO date header + + // Check for Paragraph continuation + } else if (lineType0 == NormalLine && lineType1 == NormalLine && lineType2 == NormalLine) { + continue; + + // Check for Paragraph - Single line + } else if (lineType0 != NormalLine && lineType1 == NormalLine && lineType2 == EmptyLine) { + g_addNode(ParaNode, paraLine, g_paraLineNumber); + + // Check for Paragraph - Two or more lines + } else if (lineType0 == NormalLine && lineType1 == NormalLine && lineType2 != NormalLine) { + g_addNode(ParaNode, paraLine, g_paraLineNumber); + + // Check for Header + } else if (lineType0 != NormalLine && lineType1 == NormalLine && lineType2 == HeaderLine) { + g_addNode(HeadNode, g_lineHistory.at(1), i - 1); + + // Check for Section + } else if (lineType0 != NormalLine && lineType1 == NormalLine && lineType2 == SectionLine) { + g_addNode(SectNode, g_lineHistory.at(1), i - 1); + } + } +} Index: addons/symbolviewer/plugin_katesymbolviewer.h =================================================================== --- addons/symbolviewer/plugin_katesymbolviewer.h +++ addons/symbolviewer/plugin_katesymbolviewer.h @@ -141,8 +141,8 @@ void parseXsltSymbols(void); void parsePhpSymbols(void); void parseBashSymbols(void); + void parsePlainTextSymbols(void); void parseEcmaSymbols(void); - }; class KatePluginSymbolViewer : public KTextEditor::Plugin Index: addons/symbolviewer/plugin_katesymbolviewer.cpp =================================================================== --- addons/symbolviewer/plugin_katesymbolviewer.cpp +++ addons/symbolviewer/plugin_katesymbolviewer.cpp @@ -318,6 +318,9 @@ /** Get the current highlighting mode */ QString hlModeName = doc->mode(); + // FIXME Best would be when each parser is own child class of some parser class + // and each parser is a plugin to the symbol view plugin, then could someone + // code and share his own parser without the need to re-compile Kate if (hlModeName.contains(QLatin1String("C++")) || hlModeName == QLatin1String("C") || hlModeName == QLatin1String("ANSI C89")) parseCppSymbols(); else if (hlModeName == QLatin1String("PHP (HTML)")) @@ -338,6 +341,8 @@ parseXsltSymbols(); else if (hlModeName == QLatin1String("Bash")) parseBashSymbols(); + else if (hlModeName == QLatin1String("Normal")) + parsePlainTextSymbols(); else if (hlModeName == QLatin1String("ActionScript 2.0") || hlModeName == QLatin1String("JavaScript") || hlModeName == QLatin1String("QML")) Index: addons/symbolviewer/testfile.txt =================================================================== --- /dev/null +++ addons/symbolviewer/testfile.txt @@ -0,0 +1,182 @@ +Test And Demo File For The PlainText-Parser +============================================= +With this you have something like a table of contents when reading/edit a plain +text file like a README. + + +Section +========= +Recognized right now is only === as section and --- as header starting at a +length of 3 characters, all other text is treated as paragraph. + + + +There can be any space before or after some recognized section/header/paragraph +which must not affect the desired result. + + + + +Header +-------- + + + +So this is a paragraph consist only of one line. + + - This paragraph has + - two lines + +This is a new paragraph +with a lengh +of three lines. + +There is no lengh compare done to test if the underlining fit the title lengh. +I had though about to add these check in a way that it is somehow fuzzy, because +my style is to add to more underlining characters, others prefer to fill a +complete line or 70%, 80% of a full line. + +The decision to use a min lengh of three fit to all these but of cause no +shorter headers, because that looks to me rather unlikely. +(Don't wonder, see also "Special Treatments" below) + +1 +- + +2) +-- + +For my taste works this all pretty charming when you have a well formatted file. +It works so nicely that also a bad styled formatting is treated correctly. + +Bad Formatting Example +======================== +Missed Empty Line +------------------- +Foo bar text + +^-- Well done as Section/Header/Paragraph + + +But there could be things improved. As hint what I talk about I have +inserted below some example lines. + + +Special Treatments +==================== + +Avoided Paragraphs +-------------------- +To keep the symbol list free from useless entries, will some lines treated as an +empty line. So none of the following must treated as start of a new paragraph. + +A single line of section/header characters... + +--- +=== + +...any line consists of only one charater... + +x +y +# +? +! + +...and any line with only two characters if there are as follows: + +-- +## +** +.. +// + +The sharp is obviously useful in some config file, the others I have added +without an concrete example, just only by a gut feeling. + + +ISO-Date Forced Sections +-------------------------- +I have the habit to write down some notes like a diary, starting with a ISO +date, sometimes underlined the date, sometimes not. So I added a special check +that these ISO dates are treated as if written like so... + +2018-07-19 Made first commit of my Kate patches +================================================= + +...but my entries looks this way: + +2018-07-19 +============ +Made first commit of my Kate patches to the symbol view plugin + + - Fix broken toggle actions + - Avoid unneeded update of current item + - Add a plain-text-parser + - Fix: Find new current item when symbol is in first line + +2018-07-22 + Contacted the "Kate-Devils" at there list + Nice people, but a bit taciturn + +2018-07-25 +============ +Very cool, my first patch is upstream +That was lastly faster than thought + + +Without this trick would only be the date appears in the symbol list, which is +not a sufficient information for my taste, and in one case not a section +created. What sadly not work is when an empty line is between the date and the +next paragraph. + +2018-07-22 + + Contacted the "Kate-Devils" at there list + + +2018-07-25 +============ + +Very cool, my first patch is upstream + + +The former could be added but I'm not sure if that may cause unexpected results, +because there are only three consecutive lines compared. So, no check can by +made if above the date is the line empty. To catch the ladder there would be make +a check of five lines or some other special quirk. + + +Other Seen Structuring Styles +------------------------------- +I can remeber that I have seen plain text files where was other characters used +as underlining to indicate a section or header, e.g. + +Using Sharp +########### + +Using Tilde +~~~~~~~~~~~ + +Using Asterisk +************** + +Using Caret +^^^^^^^^^^^ + +I have quickly added these as you can see, but in a (too?) very lazy way: + +Feel Free +#*=#*=#*= + +To Fix Me +-^-~-^-~- + +Perhaps not a problem but a funny feature. + + +Room For Improvements +======================= +There could be a list with items indented to the paragraph. But that's not only +an issue of parsing, There are not enough menu options to control these. But, +perhaps may this not a real problem.