diff --git a/mode/soy/soy.js b/mode/soy/soy.js
index 94ddad5f51bc2cd256e8f761310198770c61191f..ba16363638d370e01ee36d5bd3e46ebb2171bd01 100644
--- a/mode/soy/soy.js
+++ b/mode/soy/soy.js
@@ -255,7 +255,9 @@
           state.indent += config.indentUnit;
           state.soyState.push("literal");
           return "keyword";
-        } else if (match = stream.match(/^\{([\/@\\]?[\w?]*)/)) {
+
+        // A tag-keyword must be followed by whitespace or a closing tag.
+        } else if (match = stream.match(/^\{([\/@\\]?\w+\??)(?=[\s\}])/)) {
           if (match[1] != "/switch")
             state.indent += (/^(\/|(else|elseif|ifempty|case|fallbackmsg|default)$)/.test(match[1]) && state.tag != "switch" ? 1 : 2) * config.indentUnit;
           state.tag = match[1];
@@ -287,6 +289,13 @@
             state.soyState.push("param-def");
           }
           return "keyword";
+
+        // Not a tag-keyword; it's an implicit print tag.
+        } else if (stream.eat('{')) {
+          state.tag = "print";
+          state.indent += 2 * config.indentUnit;
+          state.soyState.push("tag");
+          return "keyword";
         }
 
         return tokenUntil(stream, state, /\{|\s+\/\/|\/\*/);
diff --git a/mode/soy/test.js b/mode/soy/test.js
index 7e0ed0f43633b0543617c512e7873cb8992d54ea..7cd111f2f33532e2b014f48765432a03e7f91bfa 100644
--- a/mode/soy/test.js
+++ b/mode/soy/test.js
@@ -93,6 +93,12 @@
      '[keyword {/template}]',
      '');
 
+  MT('tag-starting-with-function-call-is-not-a-keyword',
+     '[keyword {]index([variable-2&error $foo])[keyword }]',
+     '[keyword {css] [string "some-class"][keyword }]',
+     '[keyword {]css([string "some-class"])[keyword }]',
+     '');
+
   MT('allow-missing-colon-in-@param',
      '[keyword {template] [def .foo][keyword }]',
      '  [keyword {@param] [def showThing] [variable-3 bool][keyword }]',