1 //
2 // Author: Lorenzo Bettini <http://www.lorenzobettini.it>, (C) 2004-2008
3 //
4 // Copyright: See COPYING file that comes with this distribution
5 //
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10
11 #include "sourcehighlighter.h"
12 #include "highlighttoken.h"
13 #include "matchingparameters.h"
14 #include "highlightrule.h"
15 #include "formattermanager.h"
16 #include "highlightevent.h"
17 #include "highlighteventlistener.h"
18 #include "formatterparams.h"
19
20 using namespace std;
21
22 namespace srchilite {
23
24 /// represents highlighting as default
25 static HighlightToken defaultHighlightToken;
26 static HighlightEvent defaultHighlightEvent(defaultHighlightToken,
27 HighlightEvent::FORMATDEFAULT);
28
29 /// avoid generate an event if there's no one listening
30 #define GENERATE_EVENT(token, type) if (hasListeners()) notify(HighlightEvent(token, type));
31
32 #define GENERATE_DEFAULT_EVENT(s) \
33 if (hasListeners()) { \
34 defaultHighlightToken.clearMatched(); \
35 defaultHighlightToken.addMatched("default", s); \
36 notify(defaultHighlightEvent); \
37 }
38
39 #define UPDATE_START_IN_FORMATTER_PARAMS(start_index) if (formatterParams) formatterParams->start = start_index;
40
SourceHighlighter(HighlightStatePtr mainState)41 SourceHighlighter::SourceHighlighter(HighlightStatePtr mainState) :
42 mainHighlightState(mainState), currentHighlightState(mainState),
43 stateStack(HighlightStateStackPtr(new HighlightStateStack)),
44 formatterManager(0), optimize(false), suspended(false),
45 formatterParams(0) {
46 }
47
~SourceHighlighter()48 SourceHighlighter::~SourceHighlighter() {
49 }
50
highlightParagraph(const std::string & paragraph)51 void SourceHighlighter::highlightParagraph(const std::string ¶graph) {
52 std::string::const_iterator start = paragraph.begin();
53 std::string::const_iterator end = paragraph.end();
54 bool matched = true;
55 HighlightToken token;
56 MatchingParameters params;
57
58 // we're at the beginning
59 UPDATE_START_IN_FORMATTER_PARAMS(0);
60
61 // note that we go on even if the string is empty, since it is crucial
62 // to try to match also the end of buffer (some rules rely on that)
63 while (matched) {
64 matched = currentHighlightState->findBestMatch(start, end, token,
65 params);
66
67 if (matched) {
68 if (token.prefix.size()) {
69 // this is the index in the paragraph of the matched part
70 UPDATE_START_IN_FORMATTER_PARAMS((std::distance(paragraph.begin(), start)));
71 // format non matched part with the current state's default element
72 format(currentHighlightState->getDefaultElement(), token.prefix);
73 GENERATE_DEFAULT_EVENT(token.prefix);
74 }
75
76 // the length of the previous matched string in the matched elem list
77 int prevLen = 0;
78 // now format the matched strings
79 for (MatchedElements::const_iterator it = token.matched.begin(); it
80 != token.matched.end(); ++it) {
81 // this is the index in the paragraph of the matched part
82 UPDATE_START_IN_FORMATTER_PARAMS((std::distance(paragraph.begin(), start) + token.prefix.size() + prevLen));
83 format(it->first, it->second);
84 GENERATE_EVENT(token, HighlightEvent::FORMAT);
85 prevLen += it->second.size();
86 }
87
88 // now we're not at the beginning of line anymore, if we matched some chars
89 if (token.matchedSize)
90 params.beginningOfLine = false;
91
92 // check whether we must enter a new state
93 HighlightStatePtr nextState = getNextState(token);
94
95 if (nextState.get()) {
96 enterState(nextState);
97 GENERATE_EVENT(token, HighlightEvent::ENTERSTATE);
98 } else if (token.rule->getExitLevel()) {
99 // the rule requires to exit some states
100 if (token.rule->getExitLevel() < 0) {
101 exitAll();
102 } else {
103 exitState(token.rule->getExitLevel());
104 }
105 GENERATE_EVENT(token, HighlightEvent::EXITSTATE);
106 }
107
108 // advance in the string, so that the part not matched
109 // can be highlighted in the next loop
110 start += (token.prefix.size() + token.matchedSize);
111 } else {
112 // no rule matched, so we highlight it with the current state's default element
113 // provided the string is not empty (if it is empty this is really useless)
114 if (start != end) {
115 // this is the index in the paragraph of the matched part
116 UPDATE_START_IN_FORMATTER_PARAMS((std::distance(paragraph.begin(), start)));
117 const string s(start, end);
118 format(currentHighlightState->getDefaultElement(), s);
119 GENERATE_DEFAULT_EVENT(s);
120 }
121 }
122 }
123
124 if (optimize)
125 flush(); // flush what we have buffered
126 }
127
getNextState(const HighlightToken & token)128 HighlightStatePtr SourceHighlighter::getNextState(const HighlightToken &token) {
129 HighlightStatePtr nextState = token.rule->getNextState();
130
131 if (token.rule->isNested()) {
132 // we must enter another instance of the current state
133 nextState = currentHighlightState;
134 }
135
136 if (nextState.get() && nextState->getNeedsReferenceReplacement()) {
137 // perform replacement for the next state
138 // in case use the original state
139 if (nextState->getOriginalState().get()) {
140 // in case we had already performed replacements on the next state
141 nextState = nextState->getOriginalState();
142 }
143
144 HighlightStatePtr copyState = HighlightStatePtr(new HighlightState(
145 *nextState));
146 copyState->setOriginalState(nextState);
147 copyState->replaceReferences(token.matchedSubExps);
148 return copyState;
149 }
150
151 return nextState;
152 }
153
enterState(HighlightStatePtr state)154 void SourceHighlighter::enterState(HighlightStatePtr state) {
155 stateStack->push(currentHighlightState);
156 currentHighlightState = state;
157 }
158
159 /**
160 * Exits level states (-1 means exit all states)
161 * @param level
162 */
exitState(int level)163 void SourceHighlighter::exitState(int level) {
164 // remove additional levels
165 for (int l = 1; l < level; ++l)
166 stateStack->pop();
167
168 currentHighlightState = stateStack->top();
169 stateStack->pop();
170 }
171
exitAll()172 void SourceHighlighter::exitAll() {
173 currentHighlightState = mainHighlightState;
174 clearStateStack();
175 }
176
clearStateStack()177 void SourceHighlighter::clearStateStack() {
178 while (!stateStack->empty())
179 stateStack->pop();
180 }
181
format(const std::string & elem,const std::string & s)182 void SourceHighlighter::format(const std::string &elem, const std::string &s) {
183 if (suspended)
184 return;
185
186 if (!s.size())
187 return;
188
189 // the formatter is allowed to be null
190 if (formatterManager) {
191 if (!optimize) {
192 formatterManager->getFormatter(elem)->format(s, formatterParams);
193 } else {
194 // otherwise we optmize output generation: delay formatting a specific
195 // element until we deal with another element; this way strings that belong
196 // to the same element are formatted using only one tag: e.g.,
197 // <comment>/* mycomment */</comment>
198 // instead of
199 // <comment>/*</comment><comment>mycomment</comment><comment>*/</comment>
200 if (elem != currentElement) {
201 if (currentElement.size())
202 flush();
203 }
204
205 currentElement = elem;
206 currentElementBuffer << s;
207 }
208 }
209 }
210
flush()211 void SourceHighlighter::flush() {
212 if (formatterManager) {
213
214 // flush the buffer for the current element
215 formatterManager->getFormatter(currentElement)->format(
216 currentElementBuffer.str(), formatterParams);
217
218 // reset current element information
219 currentElement = "";
220 currentElementBuffer.str("");
221 }
222 }
223
224 }
225