File indexing completed on 2024-04-28 05:49:16
0001 /*************************************************************************** 0002 ecma_parser.cpp - description 0003 ------------------- 0004 begin : Feb 10 2012 0005 author : 2012 Jesse Crossen 0006 email : jesse.crossen@gmail.com 0007 ***************************************************************************/ 0008 /*************************************************************************** 0009 * * 0010 * SPDX-License-Identifier: GPL-2.0-or-later 0011 * * 0012 ***************************************************************************/ 0013 #include "plugin_katesymbolviewer.h" 0014 0015 void KatePluginSymbolViewerView::parseEcmaSymbols(void) 0016 { 0017 // make sure there is an active view to attach to 0018 if (!m_mainWindow->activeView()) { 0019 return; 0020 } 0021 0022 // a parsed class/function identifier 0023 QString identifier; 0024 // temporary characters 0025 QChar current, next, string_start = QLatin1Char('\0'); 0026 // whether we are in a multiline comment 0027 bool in_comment = false; 0028 // indices into the string 0029 int c, function_start = 0; 0030 // a list of inserted nodes with the index being the brace depth at insertion 0031 QList<QTreeWidgetItem *> nodes; 0032 0033 QTreeWidgetItem *node = nullptr; 0034 0035 if (m_treeOn->isChecked()) { 0036 m_symbols->setRootIsDecorated(1); 0037 } else { 0038 m_symbols->setRootIsDecorated(0); 0039 } 0040 0041 // read the document line by line 0042 KTextEditor::Document *kv = m_mainWindow->activeView()->document(); 0043 for (int i = 0; i < kv->lines(); i++) { 0044 // get a line to process, trimming off whitespace 0045 QString cl = kv->line(i).trimmed(); 0046 QString stripped; // the current line stripped of all comments and strings 0047 bool in_string = false; 0048 for (c = 0; c < cl.length(); c++) { 0049 // get the current character and the next 0050 current = cl.at(c); 0051 if ((c + 1) < cl.length()) { 0052 next = cl.at(c + 1); 0053 } else { 0054 next = QLatin1Char('\0'); 0055 } 0056 // skip the rest of the line if we find a line comment 0057 if ((!in_comment) && (current == QLatin1Char('/')) && (next == QLatin1Char('/'))) { 0058 break; 0059 } 0060 // open/close multiline comments 0061 if ((!in_string) && (current == QLatin1Char('/')) && (next == QLatin1Char('*'))) { 0062 in_comment = true; 0063 c++; 0064 continue; 0065 } else if ((in_comment) && (current == QLatin1Char('*')) && (next == QLatin1Char('/'))) { 0066 in_comment = false; 0067 c++; 0068 continue; 0069 } 0070 // open strings 0071 if ((!in_comment) && (!in_string)) { 0072 if ((current == QLatin1Char('\'')) || (current == QLatin1Char('"'))) { 0073 string_start = current; 0074 in_string = true; 0075 continue; 0076 } 0077 } 0078 // close strings 0079 if (in_string) { 0080 // skip escaped backslashes 0081 if ((current == QLatin1Char('\\')) && (next == QLatin1Char('\\'))) { 0082 c++; 0083 continue; 0084 } 0085 // skip escaped string closures 0086 if ((current == QLatin1Char('\\')) && (next == string_start)) { 0087 c++; 0088 continue; 0089 } else if (current == string_start) { 0090 in_string = false; 0091 continue; 0092 } 0093 } 0094 // add anything outside strings and comments to the stripped line 0095 if ((!in_comment) && (!in_string)) { 0096 stripped += current; 0097 } 0098 } 0099 0100 // scan the stripped line 0101 for (c = 0; c < stripped.length(); c++) { 0102 current = stripped.at(c); 0103 0104 // look for class definitions (for ActionScript) 0105 if ((current == QLatin1Char('c')) && (stripped.indexOf(QLatin1String("class "), c) == c)) { 0106 identifier.clear(); 0107 c += 6; 0108 for (/*c = c*/; c < stripped.length(); c++) { 0109 current = stripped.at(c); 0110 // look for the beginning of the class itself 0111 if ((current == QLatin1Char('(')) || (current == QLatin1Char('{'))) { 0112 c--; 0113 break; 0114 } else { 0115 identifier += current; 0116 } 0117 } 0118 // trim whitespace 0119 identifier = identifier.trimmed(); 0120 // get the node to add the class entry to 0121 if ((m_treeOn->isChecked()) && (!nodes.isEmpty())) { 0122 node = new QTreeWidgetItem(nodes.last()); 0123 if (m_expandOn->isChecked()) { 0124 m_symbols->expandItem(node); 0125 } 0126 } else { 0127 node = new QTreeWidgetItem(m_symbols); 0128 } 0129 // add an entry for the class 0130 node->setText(0, identifier); 0131 node->setIcon(0, m_icon_class); 0132 node->setText(1, QString::number(i, 10)); 0133 if (m_expandOn->isChecked()) { 0134 m_symbols->expandItem(node); 0135 } 0136 } // (look for classes) 0137 0138 // look for function definitions 0139 if ((current == QLatin1Char('f')) && (stripped.indexOf(QLatin1String("function "), c) == c)) { 0140 function_start = c; 0141 c += 8; 0142 // look for the beginning of the parameters 0143 identifier.clear(); 0144 for (/*c = c*/; c < stripped.length(); c++) { 0145 current = stripped.at(c); 0146 // look for the beginning of the function definition 0147 if ((current == QLatin1Char('(')) || (current == QLatin1Char('{'))) { 0148 c--; 0149 break; 0150 } else { 0151 identifier += current; 0152 } 0153 } 0154 // trim off whitespace 0155 identifier = identifier.trimmed(); 0156 // if we have an anonymous function, back up to see if it's assigned to anything 0157 if (!(identifier.length() > 0)) { 0158 QChar ch = QLatin1Char('\0'); 0159 for (int end = function_start - 1; end >= 0; end--) { 0160 ch = stripped.at(end); 0161 // skip whitespace 0162 if ((ch == QLatin1Char(' ')) || (ch == QLatin1Char('\t'))) { 0163 continue; 0164 } 0165 // if we hit an assignment or object property operator, 0166 // get the preceding identifier 0167 if ((ch == QLatin1Char('=')) || (ch == QLatin1Char(':'))) { 0168 end--; 0169 while (end >= 0) { 0170 ch = stripped.at(end); 0171 if ((ch != QLatin1Char(' ')) && (ch != QLatin1Char('\t'))) { 0172 break; 0173 } 0174 end--; 0175 } 0176 int start = end; 0177 while (start >= 0) { 0178 ch = stripped.at(start); 0179 if (((ch >= QLatin1Char('a')) && (ch <= QLatin1Char('z'))) || ((ch >= QLatin1Char('A')) && (ch <= QLatin1Char('Z'))) 0180 || ((ch >= QLatin1Char('0')) && (ch <= QLatin1Char('9'))) || (ch == QLatin1Char('_'))) { 0181 start--; 0182 } else { 0183 start++; 0184 break; 0185 } 0186 } 0187 identifier = stripped.mid(start, (end - start) + 1); 0188 break; 0189 } 0190 // if we hit something else, we're not going to be able 0191 // to read an assignment identifier 0192 else { 0193 break; 0194 } 0195 } 0196 } 0197 // if we have a function identifier, make a node 0198 if (identifier.length() > 0) { 0199 // make a node for the function 0200 QTreeWidgetItem *parent = nullptr; 0201 if (!nodes.isEmpty()) { 0202 parent = nodes.last(); 0203 } 0204 if ((m_treeOn->isChecked()) && (parent != nullptr)) { 0205 node = new QTreeWidgetItem(parent); 0206 } else { 0207 node = new QTreeWidgetItem(m_symbols); 0208 } 0209 // mark the parent as a class (if it's not the root level) 0210 if (parent != nullptr) { 0211 parent->setIcon(0, m_icon_class); 0212 // mark this function as a method of the parent 0213 node->setIcon(0, m_icon_function); 0214 } 0215 // mark root-level functions as classes 0216 else { 0217 node->setIcon(0, m_icon_class); 0218 } 0219 // add the function 0220 node->setText(0, identifier); 0221 node->setText(1, QString::number(i, 10)); 0222 if (m_expandOn->isChecked()) { 0223 m_symbols->expandItem(node); 0224 } 0225 } 0226 } // (look for functions) 0227 0228 // look for QML id: .... 0229 if (QStringView(stripped).mid(c, 3) == QLatin1String("id:")) { 0230 c += 3; 0231 identifier.clear(); 0232 // parse the id name 0233 for (/*c = c*/; c < stripped.length(); c++) { 0234 current = stripped.at(c); 0235 // look for the beginning of the id 0236 if (current == QLatin1Char(';')) { 0237 c--; 0238 break; 0239 } else { 0240 identifier += current; 0241 } 0242 } 0243 0244 identifier = identifier.trimmed(); 0245 0246 // if we have an id, make a node 0247 if (identifier.length() > 0) { 0248 QTreeWidgetItem *parent = nullptr; 0249 if (!nodes.isEmpty()) { 0250 parent = nodes.last(); 0251 } 0252 if ((m_treeOn->isChecked()) && (parent != nullptr)) { 0253 node = new QTreeWidgetItem(parent); 0254 } else { 0255 node = new QTreeWidgetItem(m_symbols); 0256 } 0257 0258 // mark the node as a class 0259 node->setIcon(0, m_icon_class); 0260 0261 // add the id 0262 node->setText(0, identifier); 0263 node->setText(1, QString::number(i, 10)); 0264 if (m_expandOn->isChecked()) { 0265 m_symbols->expandItem(node); 0266 } 0267 } 0268 } 0269 0270 // keep track of brace depth 0271 if (current == QLatin1Char('{')) { 0272 // if a node has been added at this level or above, 0273 // use it to extend the stack 0274 if (node != nullptr) { 0275 nodes.append(node); 0276 // if no node has been added, extend the last node to this depth 0277 } else if (!nodes.isEmpty()) { 0278 nodes.append(nodes.last()); 0279 } 0280 } else if (current == QLatin1Char('}')) { 0281 // pop the last node off the stack 0282 node = nullptr; 0283 if (!nodes.isEmpty()) { 0284 nodes.removeLast(); 0285 } 0286 } 0287 } // (scan the stripped line) 0288 0289 } // (iterate through lines of the document) 0290 }