Repository: alecthomas/chroma
Pull request #213 (2019-01-11 05:52:57)

Add graphql query and schema support
Pretty basic support, there are probably a heap of edge cases, but it should be a good start.

[Spec](https://facebook.github.io/graphql/)


Patch:
diff --git a/README.md b/README.md
index b332721..5c7d256 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ C | C, C#, C++, Cassandra CQL, CFEngine3, cfstatement/ColdFusion, CMake, COBOL,
 D | Dart, Diff, Django/Jinja, Docker, DTD
 E | EBNF, Elixir, Elm, EmacsLisp, Erlang
 F | Factor, Fish, Forth, Fortran, FSharp
-G | GAS, GDScript, GLSL, Genshi, Genshi HTML, Genshi Text, Gnuplot, Go, Go HTML Template, Go Text Template, Groovy
+G | GAS, GDScript, GLSL, Genshi, Genshi HTML, Genshi Text, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groovy
 H | Handlebars, Haskell, Haxe, Hexdump, HTML, HTTP, Hy 
 I | Idris, INI, Io
 J | Java, JavaScript, JSON, Jsx, Julia, Jungle
@@ -213,6 +213,16 @@ Chroma styles use the [same syntax](http://pygments.org/docs/styles/) as Pygment
 
 All Pygments styles have been converted to Chroma using the `_tools/style.py` script.
 
+When you work with one of [Chroma's styles](https://github.com/alecthomas/chroma/tree/master/styles), know that the `chroma.Background` token type provides the default style for tokens. It does so by defining a foreground color and background color. 
+
+For example, this gives each token name not defined in the style a default color of `#f8f8f8` and uses `#000000` for the highlighted code block's background:
+
+~~~go
+chroma.Background: "#f8f8f2 bg:#000000",
+~~~
+
+Also, token types in a style file are hierarchical. For instance, when `CommentSpecial` is not defined, Chroma uses the token style from `Comment`. So when several comment tokens use the same color, you'll only need to define `Comment` and override the one that has a different color.
+
 For a quick overview of the available styles and how they look, check out the [Chroma Style Gallery](https://xyproto.github.io/splash/docs/).
 
 ## Command-line interface
diff --git a/lexers/c/csharp.go b/lexers/c/csharp.go
index d59fa4f..c6a5f46 100644
--- a/lexers/c/csharp.go
+++ b/lexers/c/csharp.go
@@ -13,6 +13,7 @@ var CSharp = internal.Register(MustNewLexer(
 		Filenames: []string{"*.cs"},
 		MimeTypes: []string{"text/x-csharp"},
 		DotAll:    true,
+		EnsureNL:  true,
 	},
 	Rules{
 		"root": {
diff --git a/lexers/g/graphql.go b/lexers/g/graphql.go
new file mode 100644
index 0000000..a57e693
--- /dev/null
+++ b/lexers/g/graphql.go
@@ -0,0 +1,45 @@
+package g
+
+import (
+	. "github.com/alecthomas/chroma" // nolint
+	"github.com/alecthomas/chroma/lexers/internal"
+)
+
+// Go lexer.
+var Graphql = internal.Register(MustNewLexer(
+	&Config{
+		Name:      "GraphQL",
+		Aliases:   []string{"graphql", "graphqls", "gql"},
+		Filenames: []string{"*.graphql", "*.graphqls"},
+	},
+	Rules{
+		"root": {
+			{`(query|mutation|subscription|fragment|scalar|implements|interface|union|enum|input|type)`, KeywordDeclaration, Push("type")},
+			{`(on|extend|schema|directive|\.\.\.)`, KeywordDeclaration, nil},
+			{`(QUERY|MUTATION|SUBSCRIPTION|FIELD|FRAGMENT_DEFINITION|FRAGMENT_SPREAD|INLINE_FRAGMENT|SCHEMA|SCALAR|OBJECT|FIELD_DEFINITION|ARGUMENT_DEFINITION|INTERFACE|UNION|ENUM|ENUM_VALUE|INPUT_OBJECT|INPUT_FIELD_DEFINITION)\b`, KeywordConstant, nil},
+			{`[^\W\d]\w*`, NameProperty, nil},
+			{`\@\w+`, NameDecorator, nil},
+			{`:`, Punctuation, Push("type")},
+			{`[\(\)\{\}\[\],!\|=]`, Punctuation, nil},
+			{`\$\w+`, NameVariable, nil},
+			{`\d+i`, LiteralNumber, nil},
+			{`\d+\.\d*([Ee][-+]\d+)?i`, LiteralNumber, nil},
+			{`\.\d+([Ee][-+]\d+)?i`, LiteralNumber, nil},
+			{`\d+[Ee][-+]\d+i`, LiteralNumber, nil},
+			{`\d+(\.\d+[eE][+\-]?\d+|\.\d*|[eE][+\-]?\d+)`, LiteralNumberFloat, nil},
+			{`\.\d+([eE][+\-]?\d+)?`, LiteralNumberFloat, nil},
+			{`(0|[1-9][0-9]*)`, LiteralNumberInteger, nil},
+			{`"""[\x00-\x7F]*?"""`, LiteralString, nil},
+			{`"(\\["\\abfnrtv]|\\x[0-9a-fA-F]{2}|\\[0-7]{1,3}|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}|[^\\])"`, LiteralStringChar, nil},
+			{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
+			{`"(true|false|null)*"`, Literal, nil},
+			{`[\r\n\s]+`, Whitespace, nil},
+			{`#[^\r\n]*`, Comment, nil},
+		},
+		// Treats the next word as a class, default rules it would be a property
+		"type": {
+			{`[^\W\d]\w*`, NameClass, Pop(1)},
+			Include("root"),
+		},
+	},
+))
diff --git a/lexers/t/tradingview.go b/lexers/t/tradingview.go
index e411411..1e64ab8 100644
--- a/lexers/t/tradingview.go
+++ b/lexers/t/tradingview.go
@@ -13,6 +13,7 @@ var TradingView = internal.Register(MustNewLexer(
 		Filenames: []string{"*.tv"},
 		MimeTypes: []string{"text/x-tradingview"},
 		DotAll:    true,
+		EnsureNL:  true,
 	},
 	Rules{
 		"root": {
diff --git a/styles/rrt.go b/styles/rrt.go
index 7c76961..2ccf2ca 100644
--- a/styles/rrt.go
+++ b/styles/rrt.go
@@ -6,13 +6,15 @@ import (
 
 // Rrt style.
 var Rrt = Register(chroma.MustNewStyle("rrt", chroma.StyleEntries{
-	chroma.Comment:        "#00ff00",
-	chroma.NameFunction:   "#ffff00",
-	chroma.NameVariable:   "#eedd82",
-	chroma.NameConstant:   "#7fffd4",
-	chroma.Keyword:        "#ff0000",
-	chroma.CommentPreproc: "#e5e5e5",
-	chroma.LiteralString:  "#87ceeb",
-	chroma.KeywordType:    "#ee82ee",
-	chroma.Background:     " bg:#000000",
+	chroma.CommentPreproc:      "#e5e5e5",
+	chroma.Comment:             "#00ff00",
+	chroma.KeywordType:         "#ee82ee",
+	chroma.Keyword:             "#ff0000",
+	chroma.LiteralNumber:       "#ff6600",
+	chroma.LiteralStringSymbol: "#ff6600",
+	chroma.LiteralString:       "#87ceeb",
+	chroma.NameFunction:        "#ffff00",
+	chroma.NameConstant:        "#7fffd4",
+	chroma.NameVariable:        "#eedd82",
+	chroma.Background:          "#f8f8f2 bg:#000000",
 }))


Test patch:
diff --git a/lexers/testdata/csharp.actual b/lexers/testdata/csharp.actual
index 283f6d9..d30a0af 100644
--- a/lexers/testdata/csharp.actual
+++ b/lexers/testdata/csharp.actual
@@ -9,3 +9,5 @@ foreach (DriveInfo drive in drives)
     Console.WriteLine(dir);
   }
 }
+
+// Comment as last line should be recognised properly
\ No newline at end of file
diff --git a/lexers/testdata/csharp.expected b/lexers/testdata/csharp.expected
index 6799e22..a2a4baa 100644
--- a/lexers/testdata/csharp.expected
+++ b/lexers/testdata/csharp.expected
@@ -69,5 +69,6 @@
   {"type":"Punctuation","value":"}"},
   {"type":"Text","value":"\n"},
   {"type":"Punctuation","value":"}"},
-  {"type":"Text","value":"\n"}
+  {"type":"Text","value":"\n\n"},
+  {"type":"CommentSingle","value":"// Comment as last line should be recognised properly\n"}
 ]
diff --git a/lexers/testdata/graphql.actual b/lexers/testdata/graphql.actual
new file mode 100644
index 0000000..f66b448
--- /dev/null
+++ b/lexers/testdata/graphql.actual
@@ -0,0 +1,42 @@
+query Hero($episode: Episode, $withFriends: Boolean!) {
+  hero(episode: $episode) {
+    name
+    friends @include(if: $withFriends) {
+      name
+      ...frag
+
+      ... {
+        inlineFrag
+      }
+    }
+  }
+}
+
+fragment frag on User {
+  id
+  name
+  profilePic(size: 50)
+}
+
+# Switching to schema
+"Description for the type"
+type MyObjectType {
+  """
+  Description for field
+  Supports **multi-line** description for your [API](http://example.com)!
+  """
+  myField: String! @deprecated(reason: "Use `newField`.")
+
+  otherField(
+    "Description for argument"
+    arg: Int
+  )
+}
+
+input Foo {
+    bar [String!]! = ["baz"]
+}
+
+directive @deprecated(
+  reason: String = "No longer supported"
+) on FIELD_DEFINITION | ENUM_VALUE
diff --git a/lexers/testdata/graphql.expected b/lexers/testdata/graphql.expected
new file mode 100644
index 0000000..81b1d5b
--- /dev/null
+++ b/lexers/testdata/graphql.expected
@@ -0,0 +1,171 @@
+[
+  {"type":"KeywordDeclaration","value":"query"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameClass","value":"Hero"},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameVariable","value":"$episode"},
+  {"type":"Punctuation","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameClass","value":"Episode"},
+  {"type":"Punctuation","value":","},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"$withFriends"},
+  {"type":"Punctuation","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameClass","value":"Boolean"},
+  {"type":"Punctuation","value":"!)"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"{"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"NameProperty","value":"hero"},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameProperty","value":"episode"},
+  {"type":"Punctuation","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"$episode"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"{"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"NameClass","value":"name"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"NameProperty","value":"friends"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameDecorator","value":"@include"},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameProperty","value":"if"},
+  {"type":"Punctuation","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameVariable","value":"$withFriends"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"{"},
+  {"type":"TextWhitespace","value":"\n      "},
+  {"type":"NameClass","value":"name"},
+  {"type":"TextWhitespace","value":"\n      "},
+  {"type":"KeywordDeclaration","value":"..."},
+  {"type":"NameProperty","value":"frag"},
+  {"type":"TextWhitespace","value":"\n\n      "},
+  {"type":"KeywordDeclaration","value":"..."},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"{"},
+  {"type":"TextWhitespace","value":"\n        "},
+  {"type":"NameProperty","value":"inlineFrag"},
+  {"type":"TextWhitespace","value":"\n      "},
+  {"type":"Punctuation","value":"}"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"Punctuation","value":"}"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"Punctuation","value":"}"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"Punctuation","value":"}"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"KeywordDeclaration","value":"fragment"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameClass","value":"frag"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"KeywordDeclaration","value":"on"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameProperty","value":"User"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"{"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"NameProperty","value":"id"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"NameProperty","value":"name"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"NameProperty","value":"profilePic"},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameProperty","value":"size"},
+  {"type":"Punctuation","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameClass","value":"50"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"Punctuation","value":"}"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"Comment","value":"# Switching to schema"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"LiteralString","value":"\"Description for the type\""},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"KeywordDeclaration","value":"type"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameClass","value":"MyObjectType"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"{"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"LiteralString","value":"\"\"\"\n  Description for field\n  Supports **multi-line** description for your [API](http://example.com)!\n  \"\"\""},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"NameProperty","value":"myField"},
+  {"type":"Punctuation","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameClass","value":"String"},
+  {"type":"Punctuation","value":"!"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameDecorator","value":"@deprecated"},
+  {"type":"Punctuation","value":"("},
+  {"type":"NameProperty","value":"reason"},
+  {"type":"Punctuation","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralString","value":"\"Use `newField`.\""},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":"\n\n  "},
+  {"type":"NameClass","value":"otherField"},
+  {"type":"Punctuation","value":"("},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"LiteralString","value":"\"Description for argument\""},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"NameProperty","value":"arg"},
+  {"type":"Punctuation","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameClass","value":"Int"},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"Punctuation","value":"}"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"KeywordDeclaration","value":"input"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameClass","value":"Foo"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"{"},
+  {"type":"TextWhitespace","value":"\n    "},
+  {"type":"NameProperty","value":"bar"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"["},
+  {"type":"NameProperty","value":"String"},
+  {"type":"Punctuation","value":"!]!"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"="},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"["},
+  {"type":"LiteralString","value":"\"baz\""},
+  {"type":"Punctuation","value":"]"},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"Punctuation","value":"}"},
+  {"type":"TextWhitespace","value":"\n\n"},
+  {"type":"KeywordDeclaration","value":"directive"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameDecorator","value":"@deprecated"},
+  {"type":"Punctuation","value":"("},
+  {"type":"TextWhitespace","value":"\n  "},
+  {"type":"NameProperty","value":"reason"},
+  {"type":"Punctuation","value":":"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"NameClass","value":"String"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"="},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"LiteralString","value":"\"No longer supported\""},
+  {"type":"TextWhitespace","value":"\n"},
+  {"type":"Punctuation","value":")"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"KeywordDeclaration","value":"on"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"KeywordConstant","value":"FIELD_DEFINITION"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"Punctuation","value":"|"},
+  {"type":"TextWhitespace","value":" "},
+  {"type":"KeywordConstant","value":"ENUM_VALUE"},
+  {"type":"TextWhitespace","value":"\n"}
+]
diff --git a/lexers/testdata/tradingview.actual b/lexers/testdata/tradingview.actual
index 0bc340f..5447c47 100644
--- a/lexers/testdata/tradingview.actual
+++ b/lexers/testdata/tradingview.actual
@@ -10,3 +10,5 @@ plot(series=emaVal, style=circles, offset=2, linewidth=3)
 bgcolor(color=close > open ? orange :
      close != high[1] ? purple :
      na, transp=80)
+
+// Comment as last line should be recognised properly
\ No newline at end of file
diff --git a/lexers/testdata/tradingview.expected b/lexers/testdata/tradingview.expected
index 58fe5cf..962e265 100644
--- a/lexers/testdata/tradingview.expected
+++ b/lexers/testdata/tradingview.expected
@@ -89,5 +89,7 @@
   {"type":"Text","value":", transp"},
   {"type":"KeywordPseudo","value":"="},
   {"type":"LiteralNumber","value":"80"},
-  {"type":"Text","value":")\n"}
+  {"type":"Text","value":")\n\n"},
+  {"type":"CommentSingle","value":"// Comment as last line should be recognised properly"},
+  {"type":"Text","value":"\n"}
 ]