1 // Scintilla source code edit control
2 /** @file LexPO.cxx
3  ** Lexer for GetText Translation (PO) files.
4  **/
5 // Copyright 2012 by Colomban Wendling <ban@herbesfolles.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 // see https://www.gnu.org/software/gettext/manual/gettext.html#PO-Files for the syntax reference
9 // some details are taken from the GNU msgfmt behavior (like that indent is allows in front of lines)
10 
11 // TODO:
12 // * add keywords for flags (fuzzy, c-format, ...)
13 // * highlight formats inside c-format strings (%s, %d, etc.)
14 // * style for previous untranslated string? ("#|" comment)
15 
16 #include <stdlib.h>
17 #include <string.h>
18 #include <stdio.h>
19 #include <stdarg.h>
20 #include <assert.h>
21 #include <ctype.h>
22 
23 #include "ILexer.h"
24 #include "Scintilla.h"
25 #include "SciLexer.h"
26 
27 #include "WordList.h"
28 #include "LexAccessor.h"
29 #include "Accessor.h"
30 #include "StyleContext.h"
31 #include "CharacterSet.h"
32 #include "LexerModule.h"
33 
34 using namespace Scintilla;
35 
ColourisePODoc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * [],Accessor & styler)36 static void ColourisePODoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *[], Accessor &styler) {
37 	StyleContext sc(startPos, length, initStyle, styler);
38 	bool escaped = false;
39 	Sci_Position curLine = styler.GetLine(startPos);
40 	// the line state holds the last state on or before the line that isn't the default style
41 	int curLineState = curLine > 0 ? styler.GetLineState(curLine - 1) : SCE_PO_DEFAULT;
42 
43 	for (; sc.More(); sc.Forward()) {
44 		// whether we should leave a state
45 		switch (sc.state) {
46 			case SCE_PO_COMMENT:
47 			case SCE_PO_PROGRAMMER_COMMENT:
48 			case SCE_PO_REFERENCE:
49 			case SCE_PO_FLAGS:
50 			case SCE_PO_FUZZY:
51 				if (sc.atLineEnd)
52 					sc.SetState(SCE_PO_DEFAULT);
53 				else if (sc.state == SCE_PO_FLAGS && sc.Match("fuzzy"))
54 					// here we behave like the previous parser, but this should probably be highlighted
55 					// on its own like a keyword rather than changing the whole flags style
56 					sc.ChangeState(SCE_PO_FUZZY);
57 				break;
58 
59 			case SCE_PO_MSGCTXT:
60 			case SCE_PO_MSGID:
61 			case SCE_PO_MSGSTR:
62 				if (isspacechar(sc.ch))
63 					sc.SetState(SCE_PO_DEFAULT);
64 				break;
65 
66 			case SCE_PO_ERROR:
67 				if (sc.atLineEnd)
68 					sc.SetState(SCE_PO_DEFAULT);
69 				break;
70 
71 			case SCE_PO_MSGCTXT_TEXT:
72 			case SCE_PO_MSGID_TEXT:
73 			case SCE_PO_MSGSTR_TEXT:
74 				if (sc.atLineEnd) { // invalid inside a string
75 					if (sc.state == SCE_PO_MSGCTXT_TEXT)
76 						sc.ChangeState(SCE_PO_MSGCTXT_TEXT_EOL);
77 					else if (sc.state == SCE_PO_MSGID_TEXT)
78 						sc.ChangeState(SCE_PO_MSGID_TEXT_EOL);
79 					else if (sc.state == SCE_PO_MSGSTR_TEXT)
80 						sc.ChangeState(SCE_PO_MSGSTR_TEXT_EOL);
81 					sc.SetState(SCE_PO_DEFAULT);
82 					escaped = false;
83 				} else {
84 					if (escaped)
85 						escaped = false;
86 					else if (sc.ch == '\\')
87 						escaped = true;
88 					else if (sc.ch == '"')
89 						sc.ForwardSetState(SCE_PO_DEFAULT);
90 				}
91 				break;
92 		}
93 
94 		// whether we should enter a new state
95 		if (sc.state == SCE_PO_DEFAULT) {
96 			// forward to the first non-white character on the line
97 			bool atLineStart = sc.atLineStart;
98 			if (atLineStart) {
99 				// reset line state if it is set to comment state so empty lines don't get
100 				// comment line state, and the folding code folds comments separately,
101 				// and anyway the styling don't use line state for comments
102 				if (curLineState == SCE_PO_COMMENT)
103 					curLineState = SCE_PO_DEFAULT;
104 
105 				while (sc.More() && ! sc.atLineEnd && isspacechar(sc.ch))
106 					sc.Forward();
107 			}
108 
109 			if (atLineStart && sc.ch == '#') {
110 				if (sc.chNext == '.')
111 					sc.SetState(SCE_PO_PROGRAMMER_COMMENT);
112 				else if (sc.chNext == ':')
113 					sc.SetState(SCE_PO_REFERENCE);
114 				else if (sc.chNext == ',')
115 					sc.SetState(SCE_PO_FLAGS);
116 				else
117 					sc.SetState(SCE_PO_COMMENT);
118 			} else if (atLineStart && sc.Match("msgid")) { // includes msgid_plural
119 				sc.SetState(SCE_PO_MSGID);
120 			} else if (atLineStart && sc.Match("msgstr")) { // includes [] suffixes
121 				sc.SetState(SCE_PO_MSGSTR);
122 			} else if (atLineStart && sc.Match("msgctxt")) {
123 				sc.SetState(SCE_PO_MSGCTXT);
124 			} else if (sc.ch == '"') {
125 				if (curLineState == SCE_PO_MSGCTXT || curLineState == SCE_PO_MSGCTXT_TEXT)
126 					sc.SetState(SCE_PO_MSGCTXT_TEXT);
127 				else if (curLineState == SCE_PO_MSGID || curLineState == SCE_PO_MSGID_TEXT)
128 					sc.SetState(SCE_PO_MSGID_TEXT);
129 				else if (curLineState == SCE_PO_MSGSTR || curLineState == SCE_PO_MSGSTR_TEXT)
130 					sc.SetState(SCE_PO_MSGSTR_TEXT);
131 				else
132 					sc.SetState(SCE_PO_ERROR);
133 			} else if (! isspacechar(sc.ch))
134 				sc.SetState(SCE_PO_ERROR);
135 
136 			if (sc.state != SCE_PO_DEFAULT)
137 				curLineState = sc.state;
138 		}
139 
140 		if (sc.atLineEnd) {
141 			// Update the line state, so it can be seen by next line
142 			curLine = styler.GetLine(sc.currentPos);
143 			styler.SetLineState(curLine, curLineState);
144 		}
145 	}
146 	sc.Complete();
147 }
148 
FindNextNonEmptyLineState(Sci_PositionU startPos,Accessor & styler)149 static int FindNextNonEmptyLineState(Sci_PositionU startPos, Accessor &styler) {
150 	Sci_PositionU length = styler.Length();
151 	for (Sci_PositionU i = startPos; i < length; i++) {
152 		if (! isspacechar(styler[i])) {
153 			return styler.GetLineState(styler.GetLine(i));
154 		}
155 	}
156 	return 0;
157 }
158 
FoldPODoc(Sci_PositionU startPos,Sci_Position length,int,WordList * [],Accessor & styler)159 static void FoldPODoc(Sci_PositionU startPos, Sci_Position length, int, WordList *[], Accessor &styler) {
160 	if (! styler.GetPropertyInt("fold"))
161 		return;
162 	bool foldCompact = styler.GetPropertyInt("fold.compact") != 0;
163 	bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
164 
165 	Sci_PositionU endPos = startPos + length;
166 	Sci_Position curLine = styler.GetLine(startPos);
167 	int lineState = styler.GetLineState(curLine);
168 	int nextLineState;
169 	int level = styler.LevelAt(curLine) & SC_FOLDLEVELNUMBERMASK;
170 	int nextLevel;
171 	int visible = 0;
172 	int chNext = styler[startPos];
173 
174 	for (Sci_PositionU i = startPos; i < endPos; i++) {
175 		int ch = chNext;
176 		chNext = styler.SafeGetCharAt(i+1);
177 
178 		if (! isspacechar(ch)) {
179 			visible++;
180 		} else if ((ch == '\r' && chNext != '\n') || ch == '\n' || i+1 >= endPos) {
181 			int lvl = level;
182 			Sci_Position nextLine = curLine + 1;
183 
184 			nextLineState = styler.GetLineState(nextLine);
185 			if ((lineState != SCE_PO_COMMENT || foldComment) &&
186 					nextLineState == lineState &&
187 					FindNextNonEmptyLineState(i, styler) == lineState)
188 				nextLevel = SC_FOLDLEVELBASE + 1;
189 			else
190 				nextLevel = SC_FOLDLEVELBASE;
191 
192 			if (nextLevel > level)
193 				lvl |= SC_FOLDLEVELHEADERFLAG;
194 			if (visible == 0 && foldCompact)
195 				lvl |= SC_FOLDLEVELWHITEFLAG;
196 
197 			styler.SetLevel(curLine, lvl);
198 
199 			lineState = nextLineState;
200 			curLine = nextLine;
201 			level = nextLevel;
202 			visible = 0;
203 		}
204 	}
205 }
206 
207 static const char *const poWordListDesc[] = {
208 	0
209 };
210 
211 LexerModule lmPO(SCLEX_PO, ColourisePODoc, "po", FoldPODoc, poWordListDesc);
212