1 // Scintilla source code edit control
3 ** Lexer for GetText Translation (PO) files.
5 // Copyright 2012 by Colomban Wendling <ban@herbesfolles.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
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)
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)
24 #include "Scintilla.h"
28 #include "LexAccessor.h"
30 #include "StyleContext.h"
31 #include "CharacterSet.h"
32 #include "LexerModule.h"
34 using namespace Scintilla
;
36 static void ColourisePODoc(Sci_PositionU startPos
, Sci_Position length
, int initStyle
, WordList
*[], Accessor
&styler
) {
37 StyleContext
sc(startPos
, length
, initStyle
, styler
);
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
;
43 for (; sc
.More(); sc
.Forward()) {
44 // whether we should leave a state
47 case SCE_PO_PROGRAMMER_COMMENT
:
48 case SCE_PO_REFERENCE
:
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
);
62 if (isspacechar(sc
.ch
))
63 sc
.SetState(SCE_PO_DEFAULT
);
68 sc
.SetState(SCE_PO_DEFAULT
);
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
);
86 else if (sc
.ch
== '\\')
88 else if (sc
.ch
== '"')
89 sc
.ForwardSetState(SCE_PO_DEFAULT
);
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
;
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
;
105 while (sc
.More() && ! sc
.atLineEnd
&& isspacechar(sc
.ch
))
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
);
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
);
132 sc
.SetState(SCE_PO_ERROR
);
133 } else if (! isspacechar(sc
.ch
))
134 sc
.SetState(SCE_PO_ERROR
);
136 if (sc
.state
!= SCE_PO_DEFAULT
)
137 curLineState
= sc
.state
;
141 // Update the line state, so it can be seen by next line
142 curLine
= styler
.GetLine(sc
.currentPos
);
143 styler
.SetLineState(curLine
, curLineState
);
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
));
159 static void FoldPODoc(Sci_PositionU startPos
, Sci_Position length
, int, WordList
*[], Accessor
&styler
) {
160 if (! styler
.GetPropertyInt("fold"))
162 bool foldCompact
= styler
.GetPropertyInt("fold.compact") != 0;
163 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
165 Sci_PositionU endPos
= startPos
+ length
;
166 Sci_Position curLine
= styler
.GetLine(startPos
);
167 int lineState
= styler
.GetLineState(curLine
);
169 int level
= styler
.LevelAt(curLine
) & SC_FOLDLEVELNUMBERMASK
;
172 int chNext
= styler
[startPos
];
174 for (Sci_PositionU i
= startPos
; i
< endPos
; i
++) {
176 chNext
= styler
.SafeGetCharAt(i
+1);
178 if (! isspacechar(ch
)) {
180 } else if ((ch
== '\r' && chNext
!= '\n') || ch
== '\n' || i
+1 >= endPos
) {
182 Sci_Position nextLine
= curLine
+ 1;
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;
190 nextLevel
= SC_FOLDLEVELBASE
;
192 if (nextLevel
> level
)
193 lvl
|= SC_FOLDLEVELHEADERFLAG
;
194 if (visible
== 0 && foldCompact
)
195 lvl
|= SC_FOLDLEVELWHITEFLAG
;
197 styler
.SetLevel(curLine
, lvl
);
199 lineState
= nextLineState
;
207 static const char *const poWordListDesc
[] = {
211 LexerModule
lmPO(SCLEX_PO
, ColourisePODoc
, "po", FoldPODoc
, poWordListDesc
);