diff -rupN geany-1.30.1.orig/ctags/parsers/css.c geany-1.30.1/ctags/parsers/css.c
--- geany-1.30.1.orig/ctags/parsers/css.c	2017-03-19 18:23:44.000000000 +0100
+++ geany-1.30.1/ctags/parsers/css.c	2017-04-29 15:33:36.300053897 +0200
@@ -1,267 +1,878 @@
-/***************************************************************************
- * css.c
- * Token-based parser for CSS definitions
- * Author - Colomban Wendling <colomban@geany.org>
- * License GPL-2
- **************************************************************************/
-#include "general.h"
-
-#include <string.h> 
-#include <ctype.h> 
-
+/*
+*   Copyright (c) 2013, Colomban Wendling <ban@herbesfolles.org>
+*
+*   This source code is released for free distribution under the terms of the
+*   GNU General Public License version 2 or (at your option) any later version.
+*
+*   This module contains code for generating tags for the CSS scripting
+*   language.
+*/
+
+/*
+* Improved SCSS (based on parsers/php.c) by Robert Zajda apri 2016 ( see http://geany.robert.dvu.pl/ )
+* TODO: More cleanups
+*/
+
+/*
+*   INCLUDE FILES
+*/
+#include "general.h"  /* must always come first */
+#include "parse.h"
+#include "read.h"
+#include "vstring.h"
+#include "keyword.h"
 #include "entry.h"
-#include "parse.h" 
-#include "read.h" 
 #include "routines.h"
+#include "debug.h"
+
+
+#define SCOPE_SEPARATOR "::"
+
+
+enum {
+	KEYWORD_function,
+};
+typedef int keywordId; /* to allow KEYWORD_NONE */
+
+typedef enum {
+	ACCESS_UNDEFINED,
+	ACCESS_PRIVATE,
+	ACCESS_PROTECTED,
+	ACCESS_PUBLIC,
+	COUNT_ACCESS
+} accessType;
 
-typedef enum eCssKinds {
-	K_CLASS, K_SELECTOR, K_ID
+typedef enum {
+	IMPL_UNDEFINED,
+	IMPL_ABSTRACT,
+	COUNT_IMPL
+} implType;
+
+typedef enum {
+	K_INTERFACE,
+	K_CLASS,
+	K_TRAIT,
+	K_FUNCTION,
+	K_VARIABLE,
+	COUNT_KIND
 } cssKind;
 
-static kindOption CssKinds [] = {
+static kindOption CssKinds[COUNT_KIND] = {
 	{ true, 'c', "class",		"classes" },
-	{ true, 's', "selector",	"selectors" },
-	{ true, 'i', "id",			"identities" }
+	{ true, 'f', "function",	"functions" },
+	{ true, 'i', "interface",	"interfaces" },
+	{ true, 't', "trait",		"traits" },
+	{ true, 'v', "variable",	"variables" }
 };
 
-typedef enum {
-	/* any ASCII */
-	TOKEN_EOF = 257,
-	TOKEN_SELECTOR,
-	TOKEN_STRING
+typedef struct {
+	const char *name;
+	keywordId id;
+} keywordDesc;
+
+static const keywordDesc CssKeywordTable[] = {
+	/* keyword			keyword ID */
+	{ "@mixin",		KEYWORD_function		},
+};
+
+
+typedef enum eTokenType {
+	TOKEN_UNDEFINED,
+	TOKEN_EOF,
+	TOKEN_CHARACTER,
+	TOKEN_CLOSE_PAREN,
+	TOKEN_SEMICOLON,
+	TOKEN_COLON,
+	TOKEN_COMMA,
+	TOKEN_KEYWORD,
+	TOKEN_OPEN_PAREN,
+	TOKEN_OPERATOR,
+	TOKEN_IDENTIFIER,
+	TOKEN_STRING,
+	TOKEN_PERIOD,
+	TOKEN_OPEN_CURLY,
+	TOKEN_CLOSE_CURLY,
+	TOKEN_EQUAL_SIGN,
+	TOKEN_OPEN_SQUARE,
+	TOKEN_CLOSE_SQUARE,
+	TOKEN_VARIABLE,
+	TOKEN_SELECTOR_ID,
+	TOKEN_SELECTOR_CLASS,
+	TOKEN_MIXIN,
+	TOKEN_AMPERSAND
 } tokenType;
 
 typedef struct {
-	tokenType type;
-	vString *string;
+	tokenType		type;
+	keywordId		keyword;
+	vString *		string;
+	vString *		scope;
+	unsigned long 	lineNumber;
+	MIOPos			filePosition;
+	int 			parentKind; /* -1 if none */
 } tokenInfo;
 
+static langType Lang_css;
 
-static bool isSelectorChar (const int c)
+/* current statement details */
+static struct {
+	accessType access;
+	implType impl;
+} CurrentStatement;
+
+/* Current namespace */
+static vString *CurrentNamespace;
+
+
+static void buildCssKeywordHash (const langType language)
 {
-	/* attribute selectors are handled separately */
-	return (isalnum (c) ||
-			c == '_' || // allowed char
-			c == '-' || // allowed char
-			c == '+' || // allow all sibling in a single tag
-			c == '>' || // allow all child in a single tag
-			c == '|' || // allow namespace separator
-			c == '(' || // allow pseudo-class arguments
-			c == ')' ||
-			c == '.' || // allow classes and selectors
-			c == ':' || // allow pseudo classes
-			c == '*' || // allow globs as P + *
-			c == '#');  // allow ids
+	const size_t count = sizeof (CssKeywordTable) / sizeof (CssKeywordTable[0]);
+	size_t i;
+	for (i = 0; i < count ; i++)
+	{
+		const keywordDesc* const p = &CssKeywordTable[i];
+		addKeyword (p->name, language, (int) p->id);
+	}
 }
 
-static void parseSelector (vString *const string, const int firstChar)
+static const char *accessToString (const accessType access)
+{
+	static const char *const names[COUNT_ACCESS] = {
+		"undefined",
+		"private",
+		"protected",
+		"public"
+	};
+
+	Assert (access < COUNT_ACCESS);
+
+	return names[access];
+}
+
+static const char *implToString (const implType impl)
+{
+	static const char *const names[COUNT_IMPL] = {
+		"undefined",
+		"abstract"
+	};
+
+	Assert (impl < COUNT_IMPL);
+
+	return names[impl];
+}
+
+static void initCssEntry (tagEntryInfo *const e, const tokenInfo *const token,
+						  const cssKind kind, const accessType access)
+{
+	static vString *fullScope = NULL;
+	int parentKind = -1;
+
+	if (fullScope == NULL)
+		fullScope = vStringNew ();
+	else
+		vStringClear (fullScope);
+
+	initTagEntry (e, vStringValue (token->string), &(CssKinds[kind]));
+
+	e->lineNumber	= token->lineNumber;
+	e->filePosition	= token->filePosition;
+
+	if (access != ACCESS_UNDEFINED)
+		e->extensionFields.access = accessToString (access);
+	if (vStringLength (token->scope) > 0)
+	{
+		parentKind = token->parentKind;
+		if (vStringLength (fullScope) > 0)
+			vStringCatS (fullScope, SCOPE_SEPARATOR);
+		vStringCat (fullScope, token->scope);
+	}
+	if (vStringLength (fullScope) > 0)
+	{
+		Assert (parentKind >= 0);
+
+		e->extensionFields.scopeKind = &(CssKinds[parentKind]);
+		e->extensionFields.scopeName = vStringValue (fullScope);
+	}
+}
+
+static void makeSimpleCssTag (const tokenInfo *const token, const cssKind kind,
+							  const accessType access)
+{
+	if (CssKinds[kind].enabled)
+	{
+		tagEntryInfo e;
+
+		initCssEntry (&e, token, kind, access);
+		makeTagEntry (&e);
+	}
+}
+
+static void makeFunctionTag (const tokenInfo *const token,
+							 const vString *const arglist,
+							 const accessType access, const implType impl)
+{
+	if (CssKinds[K_FUNCTION].enabled)
+	{
+		tagEntryInfo e;
+
+		initCssEntry (&e, token, K_FUNCTION, access);
+
+//		if (impl != IMPL_UNDEFINED)
+//			e.extensionFields.implementation = implToString (impl);
+//		if (arglist)
+//			e.extensionFields.arglist = vStringValue (arglist);
+
+		makeTagEntry (&e);
+	}
+}
+
+static tokenInfo *newToken (void)
+{
+	tokenInfo *const token = xMalloc (1, tokenInfo);
+
+	token->type			= TOKEN_UNDEFINED;
+	token->keyword		= KEYWORD_NONE;
+	token->string		= vStringNew ();
+	token->scope		= vStringNew ();
+	token->lineNumber   = getInputLineNumber ();
+	token->filePosition = getInputFilePosition ();
+	token->parentKind	= -1;
+
+	return token;
+}
+
+static void deleteToken (tokenInfo *const token)
+{
+	vStringDelete (token->string);
+	vStringDelete (token->scope);
+	eFree (token);
+}
+
+static void copyToken (tokenInfo *const dest, const tokenInfo *const src,
+					   bool scope)
+{
+	dest->lineNumber = src->lineNumber;
+	dest->filePosition = src->filePosition;
+	dest->type = src->type;
+	dest->keyword = src->keyword;
+	vStringCopy(dest->string, src->string);
+	dest->parentKind = src->parentKind;
+	if (scope)
+		vStringCopy(dest->scope, src->scope);
+}
+
+#if 0
+#include <stdio.h>
+
+static const char *tokenTypeName (const tokenType type)
+{
+	switch (type)
+	{
+		case TOKEN_UNDEFINED:		return "undefined";
+		case TOKEN_EOF:				return "EOF";
+		case TOKEN_CHARACTER:		return "character";
+		case TOKEN_CLOSE_PAREN:		return "')'";
+		case TOKEN_SEMICOLON:		return "';'";
+		case TOKEN_COLON:			return "':'";
+		case TOKEN_COMMA:			return "','";
+		case TOKEN_OPEN_PAREN:		return "'('";
+		case TOKEN_OPERATOR:		return "operator";
+		case TOKEN_IDENTIFIER:		return "identifier";
+		case TOKEN_KEYWORD:			return "keyword";
+		case TOKEN_STRING:			return "string";
+		case TOKEN_PERIOD:			return "'.'";
+		case TOKEN_OPEN_CURLY:		return "'{'";
+		case TOKEN_CLOSE_CURLY:		return "'}'";
+		case TOKEN_EQUAL_SIGN:		return "'='";
+		case TOKEN_OPEN_SQUARE:		return "'['";
+		case TOKEN_CLOSE_SQUARE:	return "']'";
+		case TOKEN_VARIABLE:		return "variable";
+	}
+	return NULL;
+}
+
+static void printToken (const tokenInfo *const token)
+{
+	fprintf (stderr, "%p:\n\ttype:\t%s\n\tline:\t%lu\n\tscope:\t%s\n", (void *) token,
+			 tokenTypeName (token->type),
+			 token->lineNumber,
+			 vStringValue (token->scope));
+	switch (token->type)
+	{
+		case TOKEN_IDENTIFIER:
+		case TOKEN_STRING:
+		case TOKEN_VARIABLE:
+			fprintf (stderr, "\tcontent:\t%s\n", vStringValue (token->string));
+			break;
+
+		case TOKEN_KEYWORD:
+		{
+			size_t n = ARRAY_SIZE (CssKeywordTable);
+			size_t i;
+
+			fprintf (stderr, "\tkeyword:\t");
+			for (i = 0; i < n; i++)
+			{
+				if (CssKeywordTable[i].id == token->keyword)
+				{
+					fprintf (stderr, "%s\n", CssKeywordTable[i].name);
+					break;
+				}
+			}
+			if (i >= n)
+				fprintf (stderr, "(unknown)\n");
+		}
+
+		default: break;
+	}
+}
+#endif
+
+static void addToScope (tokenInfo *const token, const vString *const extra)
+{
+	if (vStringLength (token->scope) > 0)
+		vStringCatS (token->scope, SCOPE_SEPARATOR);
+	vStringCatS (token->scope, vStringValue (extra));
+}
+
+static bool isIdentChar (const int c)
+{
+	return (isalnum (c) || c == '_' || c == '-' || c == '@' || c >= 0x80);
+}
+
+static void parseString (vString *const string, const int delimiter)
+{
+	while (true)
+	{
+		int c = getcFromInputFile ();
+
+		if (c == '\\' && (c = getcFromInputFile ()) != EOF)
+			vStringPut (string, (char) c);
+		else if (c == EOF || c == delimiter)
+			break;
+		else
+			vStringPut (string, (char) c);
+	}
+
+}
+
+static void parseIdentifier (vString *const string, const int firstChar)
 {
 	int c = firstChar;
 	do
 	{
 		vStringPut (string, (char) c);
 		c = getcFromInputFile ();
-	} while (isSelectorChar (c));
+	} while (isIdentChar (c));
 	ungetcToInputFile (c);
 }
 
+static keywordId analyzeToken (vString *const name, langType language)
+{
+	vString *keyword = vStringNew ();
+	keywordId result;
+	vStringCopyToLower (keyword, name);
+	result = lookupKeyword (vStringValue (keyword), language);
+	vStringDelete (keyword);
+	return result;
+}
+
+static bool isSpace (int c)
+{
+	return (c == '\t' || c == ' ' || c == '\v' ||
+			c == '\n' || c == '\r' || c == '\f');
+}
+
+static int skipWhitespaces (int c)
+{
+	while (isSpace (c))
+		c = getcFromInputFile ();
+	return c;
+}
+
+static int skipSingleComment (void)
+{
+	int c;
+	do
+	{
+		c = getcFromInputFile ();
+		if (c == '\r')
+		{
+			int next = getcFromInputFile ();
+			if (next != '\n')
+				ungetcToInputFile (next);
+			else
+				c = next;
+		}
+		else
+		{
+			int next = getcFromInputFile ();
+			ungetcToInputFile (next);
+		}
+	} while (c != EOF && c != '\n' && c != '\r');
+	return c;
+}
+
 static void readToken (tokenInfo *const token)
 {
 	int c;
 
+	token->type		= TOKEN_UNDEFINED;
+	token->keyword	= KEYWORD_NONE;
 	vStringClear (token->string);
 
 getNextChar:
 
 	c = getcFromInputFile ();
-	while (isspace (c))
-		c = getcFromInputFile ();
 
-	token->type = c;
+	c = skipWhitespaces (c);
+
+	token->lineNumber   = getInputLineNumber ();
+	token->filePosition = getInputFilePosition ();
+
 	switch (c)
 	{
-		case EOF: token->type = TOKEN_EOF; break;
+		case EOF: token->type = TOKEN_EOF;					break;
+		case '(': token->type = TOKEN_OPEN_PAREN;			break;
+		case ')': token->type = TOKEN_CLOSE_PAREN;			break;
+		case ';': token->type = TOKEN_SEMICOLON;			break;
+		case ',': token->type = TOKEN_COMMA;				break;
+		case ':': token->type = TOKEN_COLON;				break;
+		case '{': token->type = TOKEN_OPEN_CURLY;			break;
+		case '}': token->type = TOKEN_CLOSE_CURLY;			break;
+		case '[': token->type = TOKEN_OPEN_SQUARE;			break;
+		case ']': token->type = TOKEN_CLOSE_SQUARE;			break;
+		case '&': token->type = TOKEN_AMPERSAND;			break;
 
-		case '\'':
-		case '"':
+		case '=':
 		{
-			const int delimiter = c;
-			do
+			int d = getcFromInputFile ();
+			if (d == '=' || d == '>')
+				token->type = TOKEN_OPERATOR;
+			else
 			{
-				vStringPut (token->string, c);
-				c = getcFromInputFile ();
-				if (c == '\\')
-					c = getcFromInputFile ();
-			}
-			while (c != EOF && c != delimiter);
-			if (c != EOF)
-				vStringPut (token->string, c);
+				ungetcToInputFile (d);
+				token->type = TOKEN_EQUAL_SIGN;
+			}
+			break;
+		}
+
+		case '\'':
+		case '"':
 			token->type = TOKEN_STRING;
+			parseString (token->string, c);
+			token->lineNumber = getInputLineNumber ();
+			token->filePosition = getInputFilePosition ();
+			break;
+
+		case '+':
+		case '-':
+		case '*':
+		case '%':
+		{
+			int d = getcFromInputFile ();
+			if (d != '=')
+				ungetcToInputFile (d);
+			token->type = TOKEN_OPERATOR;
 			break;
 		}
 
-		case '/': /* maybe comment start */
+		case '/': /* division or comment start */
 		{
 			int d = getcFromInputFile ();
-			if (d != '*')
+			if (d == '/') /* single-line comment */
 			{
-				ungetcToInputFile (d);
-				vStringPut (token->string, c);
-				token->type = c;
+				skipSingleComment ();
+				goto getNextChar;
 			}
-			else
+			else if (d == '*')
 			{
-				d = getcFromInputFile ();
 				do
 				{
-					c = d;
-					d = getcFromInputFile ();
-				}
-				while (d != EOF && ! (c == '*' && d == '/'));
+					c = skipToCharacterInInputFile ('*');
+					if (c != EOF)
+					{
+						c = getcFromInputFile ();
+						if (c == '/')
+							break;
+						else
+							ungetcToInputFile (c);
+					}
+				} while (c != EOF && c != '\0');
 				goto getNextChar;
 			}
+			else
+			{
+				if (d != '=')
+					ungetcToInputFile (d);
+				token->type = TOKEN_OPERATOR;
+			}
 			break;
 		}
 
-		default:
-			if (! isSelectorChar (c))
+		case '$': /* variable start */
+		{
+			int d = getcFromInputFile ();
+			if (! isIdentChar (d))
+			{
+				ungetcToInputFile (d);
+				token->type = TOKEN_UNDEFINED;
+			}
+			else
+			{
+				parseIdentifier (token->string, d);
+				token->type = TOKEN_VARIABLE;
+			}
+			break;
+		}
+
+		case '#': /* id start */
+		{
+			int d = getcFromInputFile ();
+			if (! isIdentChar (d))
 			{
-				vStringPut (token->string, c);
-				token->type = c;
+				ungetcToInputFile (d);
+				token->type = TOKEN_UNDEFINED;
 			}
 			else
 			{
-				parseSelector (token->string, c);
-				token->type = TOKEN_SELECTOR;
+				parseIdentifier (token->string, d);
+				token->type = TOKEN_SELECTOR_ID;
 			}
 			break;
+		}
+
+		case '.': /* class start */
+		{
+			int d = getcFromInputFile ();
+			if (! isIdentChar (d))
+			{
+				ungetcToInputFile (d);
+				token->type = TOKEN_UNDEFINED;
+			}
+			else
+			{
+				parseIdentifier (token->string, d);
+				token->type = TOKEN_SELECTOR_CLASS;
+			}
+			break;
+		}
+
+		case '@': /* mixin start */
+		{
+			int d = getcFromInputFile ();
+			if (! isIdentChar (d) || 	tolower (d)                         != 'm' ||
+										tolower ((d = getcFromInputFile ()))         != 'i' ||
+										tolower ((d = getcFromInputFile ()))         != 'x' ||
+										tolower ((d = getcFromInputFile ()))         != 'i' ||
+										tolower ((d = getcFromInputFile ()))         != 'n')
+			{
+				ungetcToInputFile (d);
+//				ungetcToInputFile (d);
+//				ungetcToInputFile (d);
+//				ungetcToInputFile (d);
+//				ungetcToInputFile (d);
+				token->type = TOKEN_UNDEFINED;
+			}
+			else
+			{
+				parseIdentifier (token->string, d);
+				token->type = TOKEN_MIXIN;
+			}
+			break;
+		}
+/**/
+
+		default:
+			if (! isIdentChar (c))
+				token->type = TOKEN_UNDEFINED;
+			else
+			{
+				parseIdentifier (token->string, c);
+				token->keyword = analyzeToken (token->string, getSourceLanguage ());
+				if (token->keyword == KEYWORD_NONE)
+					token->type = TOKEN_IDENTIFIER;
+				else
+					token->type = TOKEN_KEYWORD;
+			}
+			break;
+	}
+
+	if (token->type == TOKEN_SEMICOLON ||
+		token->type == TOKEN_OPEN_CURLY ||
+		token->type == TOKEN_CLOSE_CURLY)
+	{
+		/* reset current statement details on statement end, and when entering
+		 * a deeper scope.
+		 * it is a bit ugly to do this in readToken(), but it makes everything
+		 * a lot simpler. */
+		CurrentStatement.access = ACCESS_UNDEFINED;
+		CurrentStatement.impl = IMPL_UNDEFINED;
 	}
 }
 
-/* sets selector kind in @p kind if found, otherwise don't touches @p kind */
-static cssKind classifySelector (const vString *const selector)
+static void enterScope (tokenInfo *const parentToken,
+						const vString *const extraScope,
+						const int parentKind);
+
+static bool parseSelector (tokenInfo *const token, cssKind kind)
 {
-	size_t i;
+	tokenInfo *name;
+	bool readNext = true;
 
-	for (i = vStringLength (selector); i > 0; --i)
-	{
-		char c = vStringItem (selector, i - 1);
-		if (c == '.')
-			return K_CLASS;
-		else if (c == '#')
-			return K_ID;
+	name = newToken ();
+	copyToken (name, token, true);
+
+	readToken (token);
+	if (token->type == TOKEN_OPEN_CURLY) {
+		makeSimpleCssTag (name, kind, ACCESS_UNDEFINED);
+		enterScope (token, name->string, kind);
 	}
-	return K_SELECTOR;
+	else
+		readNext = false;
+
+	deleteToken (name);
+
+	return readNext;
 }
 
-static void findCssTags (void)
+static bool parseFunction (tokenInfo *const token, const tokenInfo *name)
 {
-	bool readNextToken = true;
-	tokenInfo token;
-
-	token.string = vStringNew ();
+	bool readNext = true;
+	accessType access = CurrentStatement.access;
+	implType impl = CurrentStatement.impl;
+	tokenInfo *nameFree = NULL;
+
+	readToken (token);
+	/* skip a possible leading ampersand (return by reference) */
+	if (token->type == TOKEN_AMPERSAND)
+		readToken (token);
 
-	do
+	if (! name)
 	{
-		if (readNextToken)
-			readToken (&token);
+		if (token->type != TOKEN_IDENTIFIER)
+			return false;
 
-		readNextToken = true;
+		name = nameFree = newToken ();
+		copyToken (nameFree, token, true);
+		readToken (token);
+	}
 
-		if (token.type == '@')
-		{ /* At-rules, from the "@" to the next block or semicolon */
-			bool useContents;
-			readToken (&token);
-			useContents = (strcmp (vStringValue (token.string), "media") == 0 ||
-						   strcmp (vStringValue (token.string), "supports") == 0);
-			while (token.type != TOKEN_EOF &&
-				   token.type != ';' && token.type != '{')
-			{
-				readToken (&token);
-			}
-			/* HACK: we *eat* the opening '{' for medias and the like so that
-			 *       the content is parsed as if it was at the root */
-			readNextToken = useContents && token.type == '{';
-		}
-		else if (token.type == TOKEN_SELECTOR)
-		{ /* collect selectors and make a tag */
-			cssKind kind = K_SELECTOR;
-			MIOPos filePosition;
-			unsigned long lineNumber;
-			vString *selector = vStringNew ();
-			do
-			{
-				if (vStringLength (selector) > 0)
-					vStringPut (selector, ' ');
-				vStringCat (selector, token.string);
-
-				kind = classifySelector (token.string);
-				lineNumber = getInputLineNumber ();
-				filePosition = getInputFilePosition ();
+	if (token->type == TOKEN_OPEN_PAREN)
+	{
+		vString *arglist = vStringNew ();
+		int depth = 1;
 
-				readToken (&token);
+		vStringPut (arglist, '(');
+		do
+		{
+			readToken (token);
 
-				/* handle attribute selectors */
-				if (token.type == '[')
+			switch (token->type)
+			{
+				case TOKEN_OPEN_PAREN:  depth++; break;
+				case TOKEN_CLOSE_PAREN: depth--; break;
+				default: break;
+			}
+			/* display part */
+			switch (token->type)
+			{
+				case TOKEN_AMPERSAND:		vStringPut (arglist, '&');		break;
+				case TOKEN_CLOSE_CURLY:		vStringPut (arglist, '}');		break;
+				case TOKEN_CLOSE_PAREN:		vStringPut (arglist, ')');		break;
+				case TOKEN_CLOSE_SQUARE:	vStringPut (arglist, ']');		break;
+				case TOKEN_COLON:			vStringPut (arglist, ':');		break;
+				case TOKEN_COMMA:			vStringCatS (arglist, ", ");	break;
+				case TOKEN_EQUAL_SIGN:		vStringCatS (arglist, " = ");	break;
+				case TOKEN_OPEN_CURLY:		vStringPut (arglist, '{');		break;
+				case TOKEN_OPEN_PAREN:		vStringPut (arglist, '(');		break;
+				case TOKEN_OPEN_SQUARE:		vStringPut (arglist, '[');		break;
+				case TOKEN_PERIOD:			vStringPut (arglist, '.');		break;
+				case TOKEN_SEMICOLON:		vStringPut (arglist, ';');		break;
+				case TOKEN_STRING:			vStringCatS (arglist, "'...'");	break;
+
+				case TOKEN_IDENTIFIER:
+				case TOKEN_KEYWORD:
+				case TOKEN_VARIABLE:
 				{
-					int depth = 1;
-					while (depth > 0 && token.type != TOKEN_EOF)
+					switch (vStringLast (arglist))
 					{
-						vStringCat (selector, token.string);
-						readToken (&token);
-						if (token.type == '[')
-							depth++;
-						else if (token.type == ']')
-							depth--;
+						case 0:
+						case ' ':
+						case '{':
+						case '(':
+						case '[':
+						case '.':
+							/* no need for a space between those and the identifier */
+							break;
+
+						default:
+							vStringPut (arglist, ' ');
+							break;
 					}
-					if (token.type != TOKEN_EOF)
-						vStringCat (selector, token.string);
-					readToken (&token);
+					if (token->type == TOKEN_VARIABLE)
+						vStringPut (arglist, '$');
+					vStringCat (arglist, token->string);
+					break;
 				}
+
+				default: break;
 			}
-			while (token.type == TOKEN_SELECTOR);
-			/* we already consumed the next token, don't read it twice */
-			readNextToken = false;
+		}
+		while (token->type != TOKEN_EOF && depth > 0);
 
-			if (CssKinds[kind].enabled)
-			{
-				tagEntryInfo e;
-				initTagEntry (&e, vStringValue (selector), &(CssKinds[kind]));
+		makeFunctionTag (name, arglist, access, impl);
+		vStringDelete (arglist);
 
-				e.lineNumber	= lineNumber;
-				e.filePosition	= filePosition;
+		readToken (token); /* normally it's an open brace or "use" keyword */
+	}
 
-				makeTagEntry (&e);
-			}
-			vStringDelete (selector);
+	if (token->type == TOKEN_OPEN_CURLY)
+		enterScope (token, name->string, K_FUNCTION);
+	else
+		readNext = false;
+
+	if (nameFree)
+		deleteToken (nameFree);
+
+	return readNext;
+}
+
+static bool parseVariable (tokenInfo *const token)
+{
+	tokenInfo *name;
+	bool readNext = true;
+	accessType access = CurrentStatement.access;
+
+	name = newToken ();
+	copyToken (name, token, true);
+
+	readToken (token);
+	if (token->type == TOKEN_COLON)
+	{
+		cssKind kind = K_VARIABLE;
+
+		readToken (token);
+		if (token->type == TOKEN_KEYWORD &&
+			token->keyword == KEYWORD_function &&
+			CssKinds[kind].enabled)
+		{
+			if (parseFunction (token, name))
+				readToken (token);
+			readNext = (bool) (token->type == TOKEN_SEMICOLON);
 		}
-		else if (token.type == '{')
-		{ /* skip over { ... } */
-			int depth = 1;
-			while (depth > 0 && token.type != TOKEN_EOF)
-			{
-				readToken (&token);
-				if (token.type == '{')
-					depth++;
-				else if (token.type == '}')
-					depth--;
-			}
+		else
+		{
+			makeSimpleCssTag (name, kind, access);
+			readNext = false;
+		}
+	}
+	else
+		readNext = false;
+
+	deleteToken (name);
+
+	return readNext;
+}
+
+static void enterScope (tokenInfo *const parentToken,
+						const vString *const extraScope,
+						const int parentKind)
+{
+	tokenInfo *token = newToken ();
+	int origParentKind = parentToken->parentKind;
+
+	copyToken (token, parentToken, true);
+
+	if (extraScope)
+	{
+		addToScope (token, extraScope);
+		token->parentKind = parentKind;
+	}
+
+	readToken (token);
+	while (token->type != TOKEN_EOF &&
+		   token->type != TOKEN_CLOSE_CURLY)
+	{
+		bool readNext = true;
+
+		switch (token->type)
+		{
+			case TOKEN_OPEN_CURLY:
+				enterScope (token, NULL, -1);
+				break;
+
+			case TOKEN_SELECTOR_ID:
+				readNext = parseSelector (token, K_INTERFACE);
+				break;
+
+			case TOKEN_SELECTOR_CLASS:
+				readNext = parseSelector (token, K_CLASS);
+				break;
+
+			case TOKEN_KEYWORD:
+				switch (token->keyword)
+				{
+					case KEYWORD_function:	readNext = parseFunction (token, NULL);				break;
+					default: break;
+				}
+				break;
+
+			case TOKEN_MIXIN:
+				readNext = parseFunction (token, NULL);
+				break;
+
+			case TOKEN_VARIABLE:
+				readNext = parseVariable (token);
+				break;
+
+			default: break;
 		}
+
+		if (readNext)
+			readToken (token);
+	}
+
+	copyToken (parentToken, token, false);
+	parentToken->parentKind = origParentKind;
+	deleteToken (token);
+}
+
+static void findTags (void)
+{
+	tokenInfo *const token = newToken ();
+
+	CurrentStatement.access = ACCESS_UNDEFINED;
+	CurrentStatement.impl = IMPL_UNDEFINED;
+	CurrentNamespace = vStringNew ();
+
+	do
+	{
+		enterScope (token, NULL, -1);
 	}
-	while (token.type != TOKEN_EOF);
+	while (token->type != TOKEN_EOF); /* keep going even with unmatched braces */
+
+	vStringDelete (CurrentNamespace);
+	deleteToken (token);
+}
 
-	vStringDelete (token.string);
+static void initializeCssParser (const langType language)
+{
+	Lang_css = language;
+	buildCssKeywordHash (language);
 }
 
-/* parser definition */
 extern parserDefinition* CssParser (void)
 {
-	static const char *const extensions [] = { "css", NULL };
+	static const char *const extensions [] = { "css", "css3", "css4", "css5", "phtml", NULL };
 	parserDefinition* def = parserNew ("CSS");
 	def->kinds      = CssKinds;
 	def->kindCount  = ARRAY_SIZE (CssKinds);
 	def->extensions = extensions;
-	def->parser     = findCssTags;
+	def->parser     = findTags;
+	def->initialize = initializeCssParser;
+	def->keywordTable = CssKeywordTable;
+	def->keywordCount = ARRAY_SIZE (CssKeywordTable);
 	return def;
 }
 
diff -rupN geany-1.30.1.orig/src/symbols.c geany-1.30.1/src/symbols.c
--- geany-1.30.1.orig/src/symbols.c	2017-03-19 18:23:44.000000000 +0100
+++ geany-1.30.1/src/symbols.c	2017-04-29 15:24:05.823414655 +0200
@@ -620,9 +620,12 @@ static void add_top_level_items(GeanyDoc
 		case GEANY_FILETYPES_CSS:
 		{
 			tag_list_add_groups(tag_store,
+				&(tv_iters.tag_interface), _("ID Selectors"), ICON_STRUCT,
 				&(tv_iters.tag_class), _("Classes"), ICON_CLASS,
-				&(tv_iters.tag_variable), _("ID Selectors"), ICON_VAR,
-				&(tv_iters.tag_struct), _("Type Selectors"), ICON_STRUCT, NULL);
+				&(tv_iters.tag_struct), _("Type Selectors"), ICON_STRUCT,
+				&(tv_iters.tag_function), _("Mixins"), ICON_METHOD,
+				&(tv_iters.tag_variable), _("Variables"), ICON_VAR,
+				NULL);
 			break;
 		}
 		case GEANY_FILETYPES_REST:
diff -rupN geany-1.30.1.orig/src/tagmanager/tm_parser.c geany-1.30.1/src/tagmanager/tm_parser.c
--- geany-1.30.1.orig/src/tagmanager/tm_parser.c	2017-03-19 18:23:44.000000000 +0100
+++ geany-1.30.1/src/tagmanager/tm_parser.c	2017-04-29 15:45:28.690019650 +0200
@@ -170,8 +170,10 @@ static TMParserMapEntry map_ERLANG[] = {
 
 static TMParserMapEntry map_CSS[] = {
 	{'c', tm_tag_class_t},
-	{'s', tm_tag_struct_t},
-	{'i', tm_tag_variable_t},
+	{'f', tm_tag_function_t},
+	{'i', tm_tag_interface_t},
+	{'t', tm_tag_struct_t},
+	{'v', tm_tag_variable_t},
 };
 
 static TMParserMapEntry map_RUBY[] = {
