@@ -228,15 +228,27 @@ whitespace :: String
228228whitespace = "WHITESPACE"
229229
230230
231+tab :: String
232+tab = "TAB"
233+
234+
231235keyword :: String -> String
232236keyword kwd =
233237 "KEYWORD=" ++ kwd
234238
235239
236−unkeyword :: String -> Maybe String
237−unkeyword message =
240+data SpecialMessage
241+ = MsgKeyword String
242+ | MsgTab
243+
244+
245+extractSpecialMessage :: String -> Maybe SpecialMessage
246+extractSpecialMessage message =
238247 if List.isPrefixOf "KEYWORD=" message then
239− Just (drop (length "KEYWORD=") message)
248+ Just $ MsgKeyword (drop (length "KEYWORD=") message)
249+
250+ else if tab == message then
251+ Just MsgTab
240252
241253 else
242254 Nothing
@@ -263,7 +275,12 @@ parseErrorReport messages =
263275 else
264276 msg
265277 in
266− hint { _expected = Set.insert msg' (_expected hint) }
278+ if msg == tab then
279+ -- Our parser looks for tab so it can throw a custom tab error message.
280+ -- Exclude tabs from the list of expected tokens so that no one thinks they are supported.
281+ hint
282+ else
283+ hint { _expected = Set.insert msg' (_expected hint) }
267284
268285 Parsec.Message msg ->
269286 hint { _messages = msg : _messages hint }
@@ -274,12 +291,17 @@ parseErrorReport messages =
274291 (preHint, maybePost) =
275292 case msgs of
276293 [msg] ->
277− case unkeyword msg of
278− Just kwd ->
294+ case extractSpecialMessage msg of
295+ Just (MsgKeyword kwd) ->
279296 ( "It looks like the keyword `" ++ kwd ++ "` is being used as a variable."
280297 , Just "Rename it to something else."
281298 )
282299
300+ Just MsgTab ->
301+ ( "A tab character was found, but all whitespace (including indentation) must be spaces not tabs."
302+ , Just "I am looking for spaces, not tabs."
303+ )
304+
283305 Nothing ->
284306 ( msg
285307 , Nothing