Rewrite alternating-font macros

Rewrite alternating-font macros

A full rewrite of what cfb7d72 attempted to do. Given the constraints of
TextMate's highlighting engine, it's no surprise that this rewrite isn't
an airtight revision. Some minor edge-cases exist, but the risk of their
discovery is extremely slim:

1. Odd-numbered arguments following a quoted string without a separating
   space can't be line-wrapped. E.g., consider the following example:

       .BI BBBBB "IIIII"BBBB\      .BI BBBBB "IIIII" BBBB\
       NOT-BOLD-BUT-SHOULD-BE      BUT-THIS-IS-FINE-THOUGH

2. Inserting a concealed newline directly after the macro's name results
   in formatting order being swapped:

       .BI\       <- WRONG         .BI \       <- OKAY
       IIIII BBBB\                 BBBBBB IIII\
       BBBBB IIIII                 IIIIII BBBBB

3. A trailing quote at the end of an odd-numbered quoted argument should
   be escaped like a double-quote, but instead receives the highlighting
   of the following argument. This only occurs if nothing else follows a
   macro call:

       .BI "BBBB""   <- Last quote italicised
       .BI "BBBB""B  <- Double-quote treated as an escape

4. Lightshow inserts an extraneous extra newline after a floating escape
   character, but it's unknown if GitHub's rendering will reproduce this
   oddity too. This also only happens if a newline is escaped after even
   arguments.

       .BI "B" II \      .BI "B" II \
       BBBB           ->
                         BBBB <- Also not highlighted

These macros are typically only used for short, simplistic highlighting.
As such, it's highly unlikely any author will encounter these bugs in an
actual roff document.

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -492,10 +492,15 @@
 			match: "^(\\.|'+)[ \t]*$"
 			captures:
 				1: {name: "punctuation.definition.comment.roff"}
-		},
+		},{
+
+			# Concealed newline
+			name: "constant.character.escape.newline.roff"
+			begin: "\\\\$\\n?"
+			end: "^"
+		}

 		# Basic sequences
-		{match: "\\\\\n",    name: "constant.character.escape.newline.roff"}
 		{match: "\\\\t",     name: "constant.character.escape.tab.roff"}
 		{match: "\\\\a",     name: "constant.character.escape.leader-char.roff"}
 		{match: "\\x5C{2}",  name: "constant.character.escape.backslash.roff"}
@@ -1081,904 +1086,340 @@
 			endCaptures:
 				1: {name: "markup.italic.roff"}
 				2: {patterns: [{include: "#escapes"}]}
-		},{
-
-			# .BI: Bold and italic
+
+		}, include: "#alternating-fonts"]
+
+
+	# Repeating/combined-font macros
+	"alternating-fonts":
+		patterns: [{
+
+			# .BI: Bold + Italic
 			name: "meta.bold-italic.roff"
-			begin: "^([.'])[ \t]*(BI)(?=[ \t\\\\]|$)"
+			begin: "^([.'])[ \t]*(BI)\\b"
+			end:   '(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
 			beginCaptures:
 				0: {name: "meta.function.man.macro.roff"}
 				1: {name: "punctuation.definition.function.macro.roff"}
 				2: {name: "entity.name.function.roff"}
-			end: "(?<!\\\\)$|(?=\\\\\")"
-			patterns: [{
-
-				begin: '((?<!\\\\)"(?:[^"\\\\]|""|\\\\(?!").)+")[ \t]*((?!\\\\?")(?:[^"\\s\\\\]|""|\\\\(?!").)+\\\\$\\n)'
-				end: '^$|(?=\\\\")|(?<!\\\\)$|(?=[ \t])'
-				contentName: "markup.italic.roff"
-				beginCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.italic.roff"
-				patterns: [
-					{match: '\\\\(?!").', name: "constant.character.escape.roff"}
-					{begin: "\\\\$\\n", end: "^"}
-				]
-			},{
-
-				begin: '((?!\\\\?")(?:[^\\s\\\\]|""|\\\\(?!").)+)[ \t]+(""|"(?!"))'
-				patterns: [include: "#string-escapes"]
-				end: '(?=\\\\")|(?:[^"\\\\]|""|\\\\(?!").)*(?:"|\\\\\\\\$)|(?<!\\\\)$'
-				contentName: "markup.italic.roff"
-				beginCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.italic.roff"
-				endCaptures:
-					0: name: "markup.italic.roff"
-			},{
-				begin: '("(?:[^\\\\"]|""|\\\\[^"])+(?=\\\\$\\n))'
-				end: '(?<!")(?=[ \t])|(?:[^\\s"\\\\]|""|\\\\(?!").)*(?:(?<!\\\\)(?:"|(?=$))|(?=\\\\"))'
-				beginCaptures: 0: name: "markup.bold.roff"
-				endCaptures:   0: name: "markup.italic.roff"
-				patterns: [{
-					name: "markup.bold.roff"
-					begin: "(?<=\\G)\\\\$\\n"
-					end:   '(?<!\\\\)"(?!")|(?<!\\\\)$|(?=\\\\")'
-					patterns: [
-						{include: "#string-escapes"}
-						{begin: '^(?:[^"\\\\]|""|\\\\(?!").)*\\\\$\\n', end: "^"}
-					]}
-					{name: "markup.bold.roff",   match: '^(?:[^\\\\"]|""|\\\\[^"])+(?<!\\\\)"'}
-					{name: "markup.italic.roff", begin: '[ \\t]+(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\[^"])+\\\\$\\n', end: '(?<!\\\\)$|(?="[ \\t])|(?=\\\\")|(?<!\\\\)(?=[ \\t])'}
-					{name: "markup.italic.roff", match: '(?:[ \\t]+|(?<=")(?=[^"]|$))(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\(?!").)+'}
-					{name: "markup.bold.roff",   begin: "\\\\$\\n", end: "^"}
-					{name: "markup.italic.roff", match: '^(?:[^"\\s\\\\]|""|\\\\(?!").)+'}]
-			},{
-				match: '(?<!\\\\)("(?:[^\\\\"]|""|\\\\(?!").)*")[ \t]+("(?:[^\\\\"]|""|\\\\(?!").)*(?:(?=[^"\\\\]$)|"))'
-				captures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.italic.roff"
-			},{
-				begin: '(?<!\\\\)("(?:[^"]|"")*"|(?:[^\\s\\\\]|\\\\.)+)[ \t]+("(?:[^"]|"")*\\\\$\\n)'
-				patterns: [
-					{name: "markup.italic.roff", match: '^(?:[^"]|"")*\\\\$\\n'}
-					{include: "#string-escapes"}
-				]
-				end: '$|(?:[^"\\\\]|""|\\\\(?!").)*(?:(?=\\\\")|\\s*"(?=$|[^"]))'
-				beginCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.italic.roff"
-				contentName: "markup.italic.roff"
-				endCaptures:
-					0: name: "markup.italic.roff"
-			},{
-				begin: '(?<=\\s")(?:[^"\\\\]|""|\\\\(?=$)|\\\\(?!").)+'
-				end:   '(?=\\\\")|(?<!\\\\)(?=$)|"[ \\t]+'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.italic.roff"
-				beginCaptures: 0: name: "markup.italic.roff"
-				endCaptures:   0: name: "markup.italic.roff"
-			},{
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end:   '(?=\\\\")|^(?=(?:[^\\s\\\\]|\\\\[^\"])+[ \t]+)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=$\\n)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=[ \t]*$\\n)|^[ \t]+((?:[^ \t\\\\]|\\\\[^\"])+)(?=[ \t]|$)|^(?=$\\n)'
-				contentName: "markup.bold.roff"
-				beginCaptures:
-					0: name: "markup.bold.roff"
-				endCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.bold.roff"
-					3: name: "markup.italic.roff"
-			},{
-
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end: '(?=[ \t]+(?:[^\\s\\\\]|\\\\[^\"])+(?:\\\\(?=\\n)|[ \t]+|\\Z))'
-				beginCaptures: 0: name: "markup.bold.roff"
-				endCaptures:   1: name: "markup.italic.roff"
-				patterns: [{
-					begin: '^(\\S+)[ \t]+(\\S+\\\\$\\n)'
-					end: '(?=[ \t])|(?<!\\\\)(?=\\n)'
-					contentName: "markup.italic.roff"
-					beginCaptures:
-						1: name: "markup.bold.roff"
-						2: name: "markup.italic.roff"
-				},{
-					contentName: "markup.bold.roff"
-					begin: "^(?=\\S)"
-					end: '([ \t]+\\S+\\\\\\$\\n)|([ \t]+\\S+)(?=[ \t]|(?<!\\\\)\\Z)'
-					endCaptures:
-						1: name: "markup.italic.roff"
-						2: name: "markup.italic.roff"
-				}]
-			},{
-				begin: '((?!")(?:[^\\s\\\\]|\\\\[^\"])+)[ \t]+(?=(?:[^\\s\\\\]|\\\\[^\"])+$)'
-				contentName: "markup.italic.roff"
-				end: '^(?=[ \t]|$\\n|\\\\\")|((?:[^\\s\\\\]|\\\\(?!").)+)(?=\\\\\"|$|[ \t])'
-				beginCaptures: 1: name: "markup.bold.roff"
-				endCaptures:   0: name: "markup.italic.roff"
-				patterns: [{
-					name: "markup.italic.roff"
-					begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\(?=$)'
-					end: "(?<=\\S)(?=[ \t]|$)|^(?=[ \t])"
-					beginCaptures: 0: name: "markup.italic.roff"
-				},{
-					name: "markup.italic.roff"
-					begin: "^\\S+"
-					end: "(?=[ \t]|$\\n)"
-				}]
-			},{
-				begin: '(?:(?<=[ \t])|\\G)((?:[^\\s\\\\]|\\\\[^\"])+)(?:$|[ \t]+\\\\$\\n)'
-				end: '(?=[ \t])|(?=(?:[^\\s\\\\]|\\\\[^\"])*$)'
-				contentName: "markup.italic.roff"
-				beginCaptures: 1: name: "markup.bold.roff"
-			},{
-				name: "markup.bold.roff"
-				match: '"(?:[^"\\\\]|""|\\\\(?!").)+(?=\\\\")'
-			},{
-				begin: '"'
-				end: '(?=\\\\")|(?<!\\\\)$|(")([ \\t]*(?!")(?:[^\\s\\\\]|\\\\(?!").)+)'
-				contentName: "markup.bold.roff"
-				patterns: [include: "#string-escapes"]
-				beginCaptures: 0: name: "markup.bold.roff"
-				endCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.italic.roff"
-			},{
-				begin: '(?!\\\\")(?=\\S)'
-				end: '(?=\\\\\")|(?:((?:[^"\\s\\\\]|""|\\\\(?!").)*)[ \t]+((?:[^"\\s\\\\]|""|\\\\(?!").)+)[ \t]*)|(?=$)'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.bold.roff"
-				endCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.italic.roff"
-			}]
-
-		},{
-
-			# .BR: Bold and Roman
+			patterns: [
+				{include: "#odd-bold"}
+				{include: "#even-italic-after-bold"}
+				{include: "#even-italic"}
+				{include: "#bridge-escapes"}
+			]
+		},{
+
+			# .BR: Bold + Roman
 			name: "meta.bold-roman.roff"
-			begin: "^([.'])[ \t]*(BR)(?=[ \t\\\\]|$)"
+			begin: "^([.'])[ \t]*(BR)\\b"
+			end:   '(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
 			beginCaptures:
 				0: {name: "meta.function.man.macro.roff"}
 				1: {name: "punctuation.definition.function.macro.roff"}
 				2: {name: "entity.name.function.roff"}
-			end: "(?<!\\\\)$|(?=\\\\\")"
-			patterns: [{
-
-				begin: '((?<!\\\\)"(?:[^"\\\\]|""|\\\\(?!").)+")[ \t]*((?!\\\\?")(?:[^"\\s\\\\]|""|\\\\(?!").)+\\\\$\\n)'
-				end: '^$|(?=\\\\")|(?<!\\\\)$|(?=[ \t])'
-				contentName: "markup.plain.roff"
-				beginCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.plain.roff"
-				patterns: [
-					{match: '\\\\(?!").', name: "constant.character.escape.roff"}
-					{begin: "\\\\$\\n", end: "^"}
-				]
-			},{
-
-				begin: '((?!\\\\?")(?:[^\\s\\\\]|""|\\\\(?!").)+)[ \t]+(""|"(?!"))'
-				patterns: [include: "#string-escapes"]
-				end: '(?=\\\\")|(?:[^"\\\\]|""|\\\\(?!").)*(?:"|\\\\\\\\$)|(?<!\\\\)$'
-				contentName: "markup.plain.roff"
-				beginCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.plain.roff"
-				endCaptures:
-					0: name: "markup.plain.roff"
-			},{
-				begin: '("(?:[^\\\\"]|""|\\\\[^"])+(?=\\\\$\\n))'
-				end: '(?<!")(?=[ \t])|(?:[^\\s"\\\\]|""|\\\\(?!").)*(?:(?<!\\\\)(?:"|(?=$))|(?=\\\\"))'
-				beginCaptures: 0: name: "markup.bold.roff"
-				endCaptures:   0: name: "markup.plain.roff"
-				patterns: [{
-					name: "markup.bold.roff"
-					begin: "(?<=\\G)\\\\$\\n"
-					end:   '(?<!\\\\)"(?!")|(?<!\\\\)$|(?=\\\\")'
-					patterns: [
-						{include: "#string-escapes"}
-						{begin: '^(?:[^"\\\\]|""|\\\\(?!").)*\\\\$\\n', end: "^"}
-					]}
-					{name: "markup.bold.roff", match: '^(?:[^\\\\"]|""|\\\\[^"])+(?<!\\\\)"'}
-					{name: "markup.plain.roff", begin: '[ \\t]+(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\[^"])+\\\\$\\n', end: '(?<!\\\\)$|(?="[ \\t])|(?=\\\\")|(?<!\\\\)(?=[ \\t])'}
-					{name: "markup.plain.roff", match: '(?:[ \\t]+|(?<=")(?=[^"]|$))(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\(?!").)+'}
-					{name: "markup.bold.roff", begin: "\\\\$\\n", end: "^"}
-					{name: "markup.plain.roff", match: '^(?:[^"\\s\\\\]|""|\\\\(?!").)+'}]
-			},{
-				match: '(?<!\\\\)("(?:[^\\\\"]|""|\\\\(?!").)*")[ \t]+("(?:[^\\\\"]|""|\\\\(?!").)*(?:(?=[^"\\\\]$)|"))'
-				captures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.plain.roff"
-			},{
-				begin: '(?<!\\\\)("(?:[^"]|"")*"|(?:[^\\s\\\\]|\\\\.)+)[ \t]+("(?:[^"]|"")*\\\\$\\n)'
-				patterns: [
-					{name: "markup.plain.roff", match: '^(?:[^"]|"")*\\\\$\\n'}
-					{include: "#string-escapes"}
-				]
-				end: '$|(?:[^"\\\\]|""|\\\\(?!").)*(?:(?=\\\\")|\\s*"(?=$|[^"]))'
-				beginCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.plain.roff"
-				contentName: "markup.plain.roff"
-				endCaptures:
-					0: name: "markup.plain.roff"
-			},{
-				begin: '(?<=\\s")(?:[^"\\\\]|""|\\\\(?=$)|\\\\(?!").)+'
-				end:   '(?=\\\\")|(?<!\\\\)(?=$)|"[ \\t]+'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.plain.roff"
-				beginCaptures: 0: name: "markup.plain.roff"
-				endCaptures:   0: name: "markup.plain.roff"
-			},{
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end:   '(?=\\\\")|^(?=(?:[^\\s\\\\]|\\\\[^\"])+[ \t]+)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=$\\n)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=[ \t]*$\\n)|^[ \t]+((?:[^ \t\\\\]|\\\\[^\"])+)(?=[ \t]|$)|^(?=$\\n)'
-				contentName: "markup.bold.roff"
-				beginCaptures:
-					0: name: "markup.bold.roff"
-				endCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.bold.roff"
-					3: name: "markup.plain.roff"
-			},{
-
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end: '(?=[ \t]+(?:[^\\s\\\\]|\\\\[^\"])+(?:\\\\(?=\\n)|[ \t]+|\\Z))'
-				beginCaptures: 0: name: "markup.bold.roff"
-				endCaptures:   1: name: "markup.plain.roff"
-				patterns: [{
-					begin: '^(\\S+)[ \t]+(\\S+\\\\$\\n)'
-					end: '(?=[ \t])|(?<!\\\\)(?=\\n)'
-					contentName: "markup.plain.roff"
-					beginCaptures:
-						1: name: "markup.bold.roff"
-						2: name: "markup.plain.roff"
-				},{
-					contentName: "markup.bold.roff"
-					begin: "^(?=\\S)"
-					end: '([ \t]+\\S+\\\\\\$\\n)|([ \t]+\\S+)(?=[ \t]|(?<!\\\\)\\Z)'
-					endCaptures:
-						1: name: "markup.plain.roff"
-						2: name: "markup.plain.roff"
-				}]
-			},{
-				begin: '((?!")(?:[^\\s\\\\]|\\\\[^\"])+)[ \t]+(?=(?:[^\\s\\\\]|\\\\[^\"])+$)'
-				contentName: "markup.plain.roff"
-				end: '^(?=[ \t]|$\\n|\\\\\")|((?:[^\\s\\\\]|\\\\(?!").)+)(?=\\\\\"|$|[ \t])'
-				beginCaptures: 1: name: "markup.bold.roff"
-				endCaptures:   0: name: "markup.plain.roff"
-				patterns: [{
-					name: "markup.plain.roff"
-					begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\(?=$)'
-					end: "(?<=\\S)(?=[ \t]|$)|^(?=[ \t])"
-					beginCaptures: 0: name: "markup.plain.roff"
-				},{
-					name: "markup.plain.roff"
-					begin: "^\\S+"
-					end: "(?=[ \t]|$\\n)"
-				}]
-			},{
-				begin: '(?:(?<=[ \t])|\\G)((?:[^\\s\\\\]|\\\\[^\"])+)(?:$|[ \t]+\\\\$\\n)'
-				end: '(?=[ \t])|(?=(?:[^\\s\\\\]|\\\\[^\"])*$)'
-				contentName: "markup.plain.roff"
-				beginCaptures: 1: name: "markup.bold.roff"
-			},{
-				name: "markup.bold.roff"
-				match: '"(?:[^"\\\\]|""|\\\\(?!").)+(?=\\\\")'
-			},{
-				begin: '"'
-				end: '(?=\\\\")|(?<!\\\\)$|(")([ \\t]*(?!")(?:[^\\s\\\\]|\\\\(?!").)+)'
-				contentName: "markup.bold.roff"
-				patterns: [include: "#string-escapes"]
-				beginCaptures: 0: name: "markup.bold.roff"
-				endCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.plain.roff"
-			},{
-				begin: '(?!\\\\")(?=\\S)'
-				end: '(?=\\\\\")|(?:((?:[^"\\s\\\\]|""|\\\\(?!").)*)[ \t]+((?:[^"\\s\\\\]|""|\\\\(?!").)+)[ \t]*)|(?=$)'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.bold.roff"
-				endCaptures:
-					1: name: "markup.bold.roff"
-					2: name: "markup.plain.roff"
-			}]
-
-		},{
-
-			# .IB: Italic and bold
+			patterns: [
+				{include: "#odd-bold"}
+				{include: "#even-roman-after-bold"}
+				{include: "#even-roman"}
+				{include: "#bridge-escapes"}
+			]
+		},{
+
+			# .IB: Italic + Bold
 			name: "meta.italic-bold.roff"
-			begin: "^([.'])[ \t]*(IB)(?=[ \t\\\\]|$)"
+			begin: "^([.'])[ \t]*(IB)\\b"
+			end:   '(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
 			beginCaptures:
 				0: {name: "meta.function.man.macro.roff"}
 				1: {name: "punctuation.definition.function.macro.roff"}
 				2: {name: "entity.name.function.roff"}
-			end: "(?<!\\\\)$|(?=\\\\\")"
-			patterns: [{
-
-				begin: '((?<!\\\\)"(?:[^"\\\\]|""|\\\\(?!").)+")[ \t]*((?!\\\\?")(?:[^"\\s\\\\]|""|\\\\(?!").)+\\\\$\\n)'
-				end: '^$|(?=\\\\")|(?<!\\\\)$|(?=[ \t])'
-				contentName: "markup.bold.roff"
-				beginCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.bold.roff"
-				patterns: [
-					{match: '\\\\(?!").', name: "constant.character.escape.roff"}
-					{begin: "\\\\$\\n", end: "^"}
-				]
-			},{
-
-				begin: '((?!\\\\?")(?:[^\\s\\\\]|""|\\\\(?!").)+)[ \t]+(""|"(?!"))'
-				patterns: [include: "#string-escapes"]
-				end: '(?=\\\\")|(?:[^"\\\\]|""|\\\\(?!").)*(?:"|\\\\\\\\$)|(?<!\\\\)$'
-				contentName: "markup.bold.roff"
-				beginCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.bold.roff"
-				endCaptures:
-					0: name: "markup.bold.roff"
-			},{
-				begin: '("(?:[^\\\\"]|""|\\\\[^"])+(?=\\\\$\\n))'
-				end: '(?<!")(?=[ \t])|(?:[^\\s"\\\\]|""|\\\\(?!").)*(?:(?<!\\\\)(?:"|(?=$))|(?=\\\\"))'
-				beginCaptures: 0: name: "markup.italic.roff"
-				endCaptures:   0: name: "markup.bold.roff"
-				patterns: [{
-					name: "markup.italic.roff"
-					begin: "(?<=\\G)\\\\$\\n"
-					end:   '(?<!\\\\)"(?!")|(?<!\\\\)$|(?=\\\\")'
-					patterns: [
-						{include: "#string-escapes"}
-						{begin: '^(?:[^"\\\\]|""|\\\\(?!").)*\\\\$\\n', end: "^"}
-					]}
-					{name: "markup.italic.roff", match: '^(?:[^\\\\"]|""|\\\\[^"])+(?<!\\\\)"'}
-					{name: "markup.bold.roff", begin: '[ \\t]+(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\[^"])+\\\\$\\n', end: '(?<!\\\\)$|(?="[ \\t])|(?=\\\\")|(?<!\\\\)(?=[ \\t])'}
-					{name: "markup.bold.roff", match: '(?:[ \\t]+|(?<=")(?=[^"]|$))(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\(?!").)+'}
-					{name: "markup.italic.roff", begin: "\\\\$\\n", end: "^"}
-					{name: "markup.bold.roff", match: '^(?:[^"\\s\\\\]|""|\\\\(?!").)+'}]
-			},{
-				match: '(?<!\\\\)("(?:[^\\\\"]|""|\\\\(?!").)*")[ \t]+("(?:[^\\\\"]|""|\\\\(?!").)*(?:(?=[^"\\\\]$)|"))'
-				captures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.bold.roff"
-			},{
-				begin: '(?<!\\\\)("(?:[^"]|"")*"|(?:[^\\s\\\\]|\\\\.)+)[ \t]+("(?:[^"]|"")*\\\\$\\n)'
-				patterns: [
-					{name: "markup.bold.roff", match: '^(?:[^"]|"")*\\\\$\\n'}
-					{include: "#string-escapes"}
-				]
-				end: '$|(?:[^"\\\\]|""|\\\\(?!").)*(?:(?=\\\\")|\\s*"(?=$|[^"]))'
-				beginCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.bold.roff"
-				contentName: "markup.bold.roff"
-				endCaptures:
-					0: name: "markup.bold.roff"
-			},{
-				begin: '(?<=\\s")(?:[^"\\\\]|""|\\\\(?=$)|\\\\(?!").)+'
-				end:   '(?=\\\\")|(?<!\\\\)(?=$)|"[ \\t]+'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.bold.roff"
-				beginCaptures: 0: name: "markup.bold.roff"
-				endCaptures:   0: name: "markup.bold.roff"
-			},{
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end:   '(?=\\\\")|^(?=(?:[^\\s\\\\]|\\\\[^\"])+[ \t]+)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=$\\n)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=[ \t]*$\\n)|^[ \t]+((?:[^ \t\\\\]|\\\\[^\"])+)(?=[ \t]|$)|^(?=$\\n)'
-				contentName: "markup.italic.roff"
-				beginCaptures:
-					0: name: "markup.italic.roff"
-				endCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.italic.roff"
-					3: name: "markup.bold.roff"
-			},{
-
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end: '(?=[ \t]+(?:[^\\s\\\\]|\\\\[^\"])+(?:\\\\(?=\\n)|[ \t]+|\\Z))'
-				beginCaptures: 0: name: "markup.italic.roff"
-				endCaptures:   1: name: "markup.bold.roff"
-				patterns: [{
-					begin: '^(\\S+)[ \t]+(\\S+\\\\$\\n)'
-					end: '(?=[ \t])|(?<!\\\\)(?=\\n)'
-					contentName: "markup.bold.roff"
-					beginCaptures:
-						1: name: "markup.italic.roff"
-						2: name: "markup.bold.roff"
-				},{
-					contentName: "markup.italic.roff"
-					begin: "^(?=\\S)"
-					end: '([ \t]+\\S+\\\\\\$\\n)|([ \t]+\\S+)(?=[ \t]|(?<!\\\\)\\Z)'
-					endCaptures:
-						1: name: "markup.bold.roff"
-						2: name: "markup.bold.roff"
-				}]
-			},{
-				begin: '((?!")(?:[^\\s\\\\]|\\\\[^\"])+)[ \t]+(?=(?:[^\\s\\\\]|\\\\[^\"])+$)'
-				contentName: "markup.bold.roff"
-				end: '^(?=[ \t]|$\\n|\\\\\")|((?:[^\\s\\\\]|\\\\(?!").)+)(?=\\\\\"|$|[ \t])'
-				beginCaptures: 1: name: "markup.italic.roff"
-				endCaptures:   0: name: "markup.bold.roff"
-				patterns: [{
-					name: "markup.bold.roff"
-					begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\(?=$)'
-					end: "(?<=\\S)(?=[ \t]|$)|^(?=[ \t])"
-					beginCaptures: 0: name: "markup.bold.roff"
-				},{
-					name: "markup.bold.roff"
-					begin: "^\\S+"
-					end: "(?=[ \t]|$\\n)"
-				}]
-			},{
-				begin: '(?:(?<=[ \t])|\\G)((?:[^\\s\\\\]|\\\\[^\"])+)(?:$|[ \t]+\\\\$\\n)'
-				end: '(?=[ \t])|(?=(?:[^\\s\\\\]|\\\\[^\"])*$)'
-				contentName: "markup.bold.roff"
-				beginCaptures: 1: name: "markup.italic.roff"
-			},{
-				name: "markup.italic.roff"
-				match: '"(?:[^"\\\\]|""|\\\\(?!").)+(?=\\\\")'
-			},{
-				begin: '"'
-				end: '(?=\\\\")|(?<!\\\\)$|(")([ \\t]*(?!")(?:[^\\s\\\\]|\\\\(?!").)+)'
-				contentName: "markup.italic.roff"
-				patterns: [include: "#string-escapes"]
-				beginCaptures: 0: name: "markup.italic.roff"
-				endCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.bold.roff"
-			},{
-				begin: '(?!\\\\")(?=\\S)'
-				end: '(?=\\\\\")|(?:((?:[^"\\s\\\\]|""|\\\\(?!").)*)[ \t]+((?:[^"\\s\\\\]|""|\\\\(?!").)+)[ \t]*)|(?=$)'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.italic.roff"
-				endCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.bold.roff"
-			}]
-
-		},{
-
-			# .IR: Italic and Roman
+			patterns: [
+				{include: "#odd-italic"}
+				{include: "#even-bold-after-italic"}
+				{include: "#even-bold"}
+				{include: "#bridge-escapes"}
+			]
+		},{
+
+			# .IR: Italic + Roman
 			name: "meta.italic-roman.roff"
-			begin: "^([.'])[ \t]*(IR)(?=[ \t\\\\]|$)"
+			begin: "^([.'])[ \t]*(IR)\\b"
+			end:   '(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
 			beginCaptures:
 				0: {name: "meta.function.man.macro.roff"}
 				1: {name: "punctuation.definition.function.macro.roff"}
 				2: {name: "entity.name.function.roff"}
-			end: "(?<!\\\\)$|(?=\\\\\")"
-			patterns: [{
-
-				begin: '((?<!\\\\)"(?:[^"\\\\]|""|\\\\(?!").)+")[ \t]*((?!\\\\?")(?:[^"\\s\\\\]|""|\\\\(?!").)+\\\\$\\n)'
-				end: '^$|(?=\\\\")|(?<!\\\\)$|(?=[ \t])'
-				contentName: "markup.plain.roff"
-				beginCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.plain.roff"
-				patterns: [
-					{match: '\\\\(?!").', name: "constant.character.escape.roff"}
-					{begin: "\\\\$\\n", end: "^"}
-				]
-			},{
-
-				begin: '((?!\\\\?")(?:[^\\s\\\\]|""|\\\\(?!").)+)[ \t]+(""|"(?!"))'
-				patterns: [include: "#string-escapes"]
-				end: '(?=\\\\")|(?:[^"\\\\]|""|\\\\(?!").)*(?:"|\\\\\\\\$)|(?<!\\\\)$'
-				contentName: "markup.plain.roff"
-				beginCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.plain.roff"
-				endCaptures:
-					0: name: "markup.plain.roff"
-			},{
-				begin: '("(?:[^\\\\"]|""|\\\\[^"])+(?=\\\\$\\n))'
-				end: '(?<!")(?=[ \t])|(?:[^\\s"\\\\]|""|\\\\(?!").)*(?:(?<!\\\\)(?:"|(?=$))|(?=\\\\"))'
-				beginCaptures: 0: name: "markup.italic.roff"
-				endCaptures:   0: name: "markup.plain.roff"
-				patterns: [{
-					name: "markup.italic.roff"
-					begin: "(?<=\\G)\\\\$\\n"
-					end:   '(?<!\\\\)"(?!")|(?<!\\\\)$|(?=\\\\")'
-					patterns: [
-						{include: "#string-escapes"}
-						{begin: '^(?:[^"\\\\]|""|\\\\(?!").)*\\\\$\\n', end: "^"}
-					]}
-					{name: "markup.italic.roff", match: '^(?:[^\\\\"]|""|\\\\[^"])+(?<!\\\\)"'}
-					{name: "markup.plain.roff", begin: '[ \\t]+(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\[^"])+\\\\$\\n', end: '(?<!\\\\)$|(?="[ \\t])|(?=\\\\")|(?<!\\\\)(?=[ \\t])'}
-					{name: "markup.plain.roff", match: '(?:[ \\t]+|(?<=")(?=[^"]|$))(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\(?!").)+'}
-					{name: "markup.italic.roff", begin: "\\\\$\\n", end: "^"}
-					{name: "markup.plain.roff", match: '^(?:[^"\\s\\\\]|""|\\\\(?!").)+'}]
-			},{
-				match: '(?<!\\\\)("(?:[^\\\\"]|""|\\\\(?!").)*")[ \t]+("(?:[^\\\\"]|""|\\\\(?!").)*(?:(?=[^"\\\\]$)|"))'
-				captures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.plain.roff"
-			},{
-				begin: '(?<!\\\\)("(?:[^"]|"")*"|(?:[^\\s\\\\]|\\\\.)+)[ \t]+("(?:[^"]|"")*\\\\$\\n)'
-				patterns: [
-					{name: "markup.plain.roff", match: '^(?:[^"]|"")*\\\\$\\n'}
-					{include: "#string-escapes"}
-				]
-				end: '$|(?:[^"\\\\]|""|\\\\(?!").)*(?:(?=\\\\")|\\s*"(?=$|[^"]))'
-				beginCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.plain.roff"
-				contentName: "markup.plain.roff"
-				endCaptures:
-					0: name: "markup.plain.roff"
-			},{
-				begin: '(?<=\\s")(?:[^"\\\\]|""|\\\\(?=$)|\\\\(?!").)+'
-				end:   '(?=\\\\")|(?<!\\\\)(?=$)|"[ \\t]+'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.plain.roff"
-				beginCaptures: 0: name: "markup.plain.roff"
-				endCaptures:   0: name: "markup.plain.roff"
-			},{
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end:   '(?=\\\\")|^(?=(?:[^\\s\\\\]|\\\\[^\"])+[ \t]+)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=$\\n)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=[ \t]*$\\n)|^[ \t]+((?:[^ \t\\\\]|\\\\[^\"])+)(?=[ \t]|$)|^(?=$\\n)'
-				contentName: "markup.italic.roff"
-				beginCaptures:
-					0: name: "markup.italic.roff"
-				endCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.italic.roff"
-					3: name: "markup.plain.roff"
-			},{
-
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end: '(?=[ \t]+(?:[^\\s\\\\]|\\\\[^\"])+(?:\\\\(?=\\n)|[ \t]+|\\Z))'
-				beginCaptures: 0: name: "markup.italic.roff"
-				endCaptures:   1: name: "markup.plain.roff"
-				patterns: [{
-					begin: '^(\\S+)[ \t]+(\\S+\\\\$\\n)'
-					end: '(?=[ \t])|(?<!\\\\)(?=\\n)'
-					contentName: "markup.plain.roff"
-					beginCaptures:
-						1: name: "markup.italic.roff"
-						2: name: "markup.plain.roff"
-				},{
-					contentName: "markup.italic.roff"
-					begin: "^(?=\\S)"
-					end: '([ \t]+\\S+\\\\\\$\\n)|([ \t]+\\S+)(?=[ \t]|(?<!\\\\)\\Z)'
-					endCaptures:
-						1: name: "markup.plain.roff"
-						2: name: "markup.plain.roff"
-				}]
-			},{
-				begin: '((?!")(?:[^\\s\\\\]|\\\\[^\"])+)[ \t]+(?=(?:[^\\s\\\\]|\\\\[^\"])+$)'
-				contentName: "markup.plain.roff"
-				end: '^(?=[ \t]|$\\n|\\\\\")|((?:[^\\s\\\\]|\\\\(?!").)+)(?=\\\\\"|$|[ \t])'
-				beginCaptures: 1: name: "markup.italic.roff"
-				endCaptures:   0: name: "markup.plain.roff"
-				patterns: [{
-					name: "markup.plain.roff"
-					begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\(?=$)'
-					end: "(?<=\\S)(?=[ \t]|$)|^(?=[ \t])"
-					beginCaptures: 0: name: "markup.plain.roff"
-				},{
-					name: "markup.plain.roff"
-					begin: "^\\S+"
-					end: "(?=[ \t]|$\\n)"
-				}]
-			},{
-				begin: '(?:(?<=[ \t])|\\G)((?:[^\\s\\\\]|\\\\[^\"])+)(?:$|[ \t]+\\\\$\\n)'
-				end: '(?=[ \t])|(?=(?:[^\\s\\\\]|\\\\[^\"])*$)'
-				contentName: "markup.plain.roff"
-				beginCaptures: 1: name: "markup.italic.roff"
-			},{
-				name: "markup.italic.roff"
-				match: '"(?:[^"\\\\]|""|\\\\(?!").)+(?=\\\\")'
-			},{
-				begin: '"'
-				end: '(?=\\\\")|(?<!\\\\)$|(")([ \\t]*(?!")(?:[^\\s\\\\]|\\\\(?!").)+)'
-				contentName: "markup.italic.roff"
-				patterns: [include: "#string-escapes"]
-				beginCaptures: 0: name: "markup.italic.roff"
-				endCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.plain.roff"
-			},{
-				begin: '(?!\\\\")(?=\\S)'
-				end: '(?=\\\\\")|(?:((?:[^"\\s\\\\]|""|\\\\(?!").)*)[ \t]+((?:[^"\\s\\\\]|""|\\\\(?!").)+)[ \t]*)|(?=$)'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.italic.roff"
-				endCaptures:
-					1: name: "markup.italic.roff"
-					2: name: "markup.plain.roff"
-			}]
-
-		},{
-
-			# .RB: Roman and bold
+			patterns: [
+				{include: "#odd-italic"}
+				{include: "#even-roman-after-italic"}
+				{include: "#even-roman"}
+				{include: "#bridge-escapes"}
+			]
+		},{
+
+			# .RB: Roman + Bold
 			name: "meta.roman-bold.roff"
-			begin: "^([.'])[ \t]*(RB)(?=[ \t\\\\]|$)"
+			begin: "^([.'])[ \t]*(RB)\\b"
+			end:   '(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
 			beginCaptures:
 				0: {name: "meta.function.man.macro.roff"}
 				1: {name: "punctuation.definition.function.macro.roff"}
 				2: {name: "entity.name.function.roff"}
-			end: "(?<!\\\\)$|(?=\\\\\")"
-			patterns: [{
-
-				begin: '((?<!\\\\)"(?:[^"\\\\]|""|\\\\(?!").)+")[ \t]*((?!\\\\?")(?:[^"\\s\\\\]|""|\\\\(?!").)+\\\\$\\n)'
-				end: '^$|(?=\\\\")|(?<!\\\\)$|(?=[ \t])'
-				contentName: "markup.bold.roff"
-				beginCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.bold.roff"
-				patterns: [
-					{match: '\\\\(?!").', name: "constant.character.escape.roff"}
-					{begin: "\\\\$\\n", end: "^"}
-				]
-			},{
-
-				begin: '((?!\\\\?")(?:[^\\s\\\\]|""|\\\\(?!").)+)[ \t]+(""|"(?!"))'
-				patterns: [include: "#string-escapes"]
-				end: '(?=\\\\")|(?:[^"\\\\]|""|\\\\(?!").)*(?:"|\\\\\\\\$)|(?<!\\\\)$'
-				contentName: "markup.bold.roff"
-				beginCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.bold.roff"
-				endCaptures:
-					0: name: "markup.bold.roff"
-			},{
-				begin: '("(?:[^\\\\"]|""|\\\\[^"])+(?=\\\\$\\n))'
-				end: '(?<!")(?=[ \t])|(?:[^\\s"\\\\]|""|\\\\(?!").)*(?:(?<!\\\\)(?:"|(?=$))|(?=\\\\"))'
-				beginCaptures: 0: name: "markup.plain.roff"
-				endCaptures:   0: name: "markup.bold.roff"
-				patterns: [{
-					name: "markup.plain.roff"
-					begin: "(?<=\\G)\\\\$\\n"
-					end:   '(?<!\\\\)"(?!")|(?<!\\\\)$|(?=\\\\")'
-					patterns: [
-						{include: "#string-escapes"}
-						{begin: '^(?:[^"\\\\]|""|\\\\(?!").)*\\\\$\\n', end: "^"}
-					]}
-					{name: "markup.plain.roff", match: '^(?:[^\\\\"]|""|\\\\[^"])+(?<!\\\\)"'}
-					{name: "markup.bold.roff", begin: '[ \\t]+(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\[^"])+\\\\$\\n', end: '(?<!\\\\)$|(?="[ \\t])|(?=\\\\")|(?<!\\\\)(?=[ \\t])'}
-					{name: "markup.bold.roff", match: '(?:[ \\t]+|(?<=")(?=[^"]|$))(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\(?!").)+'}
-					{name: "markup.plain.roff", begin: "\\\\$\\n", end: "^"}
-					{name: "markup.bold.roff", match: '^(?:[^"\\s\\\\]|""|\\\\(?!").)+'}]
-			},{
-				match: '(?<!\\\\)("(?:[^\\\\"]|""|\\\\(?!").)*")[ \t]+("(?:[^\\\\"]|""|\\\\(?!").)*(?:(?=[^"\\\\]$)|"))'
-				captures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.bold.roff"
-			},{
-				begin: '(?<!\\\\)("(?:[^"]|"")*"|(?:[^\\s\\\\]|\\\\.)+)[ \t]+("(?:[^"]|"")*\\\\$\\n)'
-				patterns: [
-					{name: "markup.bold.roff", match: '^(?:[^"]|"")*\\\\$\\n'}
-					{include: "#string-escapes"}
-				]
-				end: '$|(?:[^"\\\\]|""|\\\\(?!").)*(?:(?=\\\\")|\\s*"(?=$|[^"]))'
-				beginCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.bold.roff"
-				contentName: "markup.bold.roff"
-				endCaptures:
-					0: name: "markup.bold.roff"
-			},{
-				begin: '(?<=\\s")(?:[^"\\\\]|""|\\\\(?=$)|\\\\(?!").)+'
-				end:   '(?=\\\\")|(?<!\\\\)(?=$)|"[ \\t]+'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.bold.roff"
-				beginCaptures: 0: name: "markup.bold.roff"
-				endCaptures:   0: name: "markup.bold.roff"
-			},{
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end:   '(?=\\\\")|^(?=(?:[^\\s\\\\]|\\\\[^\"])+[ \t]+)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=$\\n)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=[ \t]*$\\n)|^[ \t]+((?:[^ \t\\\\]|\\\\[^\"])+)(?=[ \t]|$)|^(?=$\\n)'
-				contentName: "markup.plain.roff"
-				beginCaptures:
-					0: name: "markup.plain.roff"
-				endCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.plain.roff"
-					3: name: "markup.bold.roff"
-			},{
-
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end: '(?=[ \t]+(?:[^\\s\\\\]|\\\\[^\"])+(?:\\\\(?=\\n)|[ \t]+|\\Z))'
-				beginCaptures: 0: name: "markup.plain.roff"
-				endCaptures:   1: name: "markup.bold.roff"
-				patterns: [{
-					begin: '^(\\S+)[ \t]+(\\S+\\\\$\\n)'
-					end: '(?=[ \t])|(?<!\\\\)(?=\\n)'
-					contentName: "markup.bold.roff"
-					beginCaptures:
-						1: name: "markup.plain.roff"
-						2: name: "markup.bold.roff"
-				},{
-					contentName: "markup.plain.roff"
-					begin: "^(?=\\S)"
-					end: '([ \t]+\\S+\\\\\\$\\n)|([ \t]+\\S+)(?=[ \t]|(?<!\\\\)\\Z)'
-					endCaptures:
-						1: name: "markup.bold.roff"
-						2: name: "markup.bold.roff"
-				}]
-			},{
-				begin: '((?!")(?:[^\\s\\\\]|\\\\[^\"])+)[ \t]+(?=(?:[^\\s\\\\]|\\\\[^\"])+$)'
-				contentName: "markup.bold.roff"
-				end: '^(?=[ \t]|$\\n|\\\\\")|((?:[^\\s\\\\]|\\\\(?!").)+)(?=\\\\\"|$|[ \t])'
-				beginCaptures: 1: name: "markup.plain.roff"
-				endCaptures:   0: name: "markup.bold.roff"
-				patterns: [{
-					name: "markup.bold.roff"
-					begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\(?=$)'
-					end: "(?<=\\S)(?=[ \t]|$)|^(?=[ \t])"
-					beginCaptures: 0: name: "markup.bold.roff"
-				},{
-					name: "markup.bold.roff"
-					begin: "^\\S+"
-					end: "(?=[ \t]|$\\n)"
-				}]
-			},{
-				begin: '(?:(?<=[ \t])|\\G)((?:[^\\s\\\\]|\\\\[^\"])+)(?:$|[ \t]+\\\\$\\n)'
-				end: '(?=[ \t])|(?=(?:[^\\s\\\\]|\\\\[^\"])*$)'
-				contentName: "markup.bold.roff"
-				beginCaptures: 1: name: "markup.plain.roff"
-			},{
-				name: "markup.plain.roff"
-				match: '"(?:[^"\\\\]|""|\\\\(?!").)+(?=\\\\")'
-			},{
-				begin: '"'
-				end: '(?=\\\\")|(?<!\\\\)$|(")([ \\t]*(?!")(?:[^\\s\\\\]|\\\\(?!").)+)'
-				contentName: "markup.plain.roff"
-				patterns: [include: "#string-escapes"]
-				beginCaptures: 0: name: "markup.plain.roff"
-				endCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.bold.roff"
-			},{
-				begin: '(?!\\\\")(?=\\S)'
-				end: '(?=\\\\\")|(?:((?:[^"\\s\\\\]|""|\\\\(?!").)*)[ \t]+((?:[^"\\s\\\\]|""|\\\\(?!").)+)[ \t]*)|(?=$)'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.plain.roff"
-				endCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.bold.roff"
-			}]
-
-		},{
-
-			# .RI: Roman and italic
+			patterns: [
+				{include: "#odd-roman"}
+				{include: "#even-bold-after-roman"}
+				{include: "#even-bold"}
+				{include: "#bridge-escapes"}
+			]
+		},{
+
+			# .RI: Roman + Italic
 			name: "meta.roman-italic.roff"
-			begin: "^([.'])[ \t]*(RI)(?=[ \t\\\\]|$)"
+			begin: "^([.'])[ \t]*(RI)\\b"
+			end:   '(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
 			beginCaptures:
 				0: {name: "meta.function.man.macro.roff"}
 				1: {name: "punctuation.definition.function.macro.roff"}
 				2: {name: "entity.name.function.roff"}
-			end: "(?<!\\\\)$|(?=\\\\\")"
+			patterns: [
+				{include: "#odd-roman"}
+				{include: "#even-italic-after-roman"}
+				{include: "#even-italic"}
+				{include: "#bridge-escapes"}
+			]
+		}]
+
+
+	"bridge-escapes":
+		patterns: [
+			{name: "constant.character.escape.newline.roff", begin: "[ \t]+\\\\$\\n?", end: "^"}
+			{name: "constant.character.escape.newline.roff", begin: "\\\\$\\n?", end: "^[ \t]*"}
+		]
+
+
+	"odd-bold":
+		patterns: [{
+			name: "markup.bold.roff"
+			begin: '[ \t]+(")'
+			end:   '(")[ \t]*|(?=\\\\")|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)'
+			beginCaptures: 1: name: "punctuation.definition.string.begin.roff"
+			endCaptures:   1: name: "punctuation.definition.string.end.roff"
 			patterns: [{
-
-				begin: '((?<!\\\\)"(?:[^"\\\\]|""|\\\\(?!").)+")[ \t]*((?!\\\\?")(?:[^"\\s\\\\]|""|\\\\(?!").)+\\\\$\\n)'
-				end: '^$|(?=\\\\")|(?<!\\\\)$|(?=[ \t])'
-				contentName: "markup.italic.roff"
-				beginCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.italic.roff"
-				patterns: [
-					{match: '\\\\(?!").', name: "constant.character.escape.roff"}
-					{begin: "\\\\$\\n", end: "^"}
-				]
-			},{
-
-				begin: '((?!\\\\?")(?:[^\\s\\\\]|""|\\\\(?!").)+)[ \t]+(""|"(?!"))'
-				patterns: [include: "#string-escapes"]
-				end: '(?=\\\\")|(?:[^"\\\\]|""|\\\\(?!").)*(?:"|\\\\\\\\$)|(?<!\\\\)$'
-				contentName: "markup.italic.roff"
-				beginCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.italic.roff"
-				endCaptures:
-					0: name: "markup.italic.roff"
-			},{
-				begin: '("(?:[^\\\\"]|""|\\\\[^"])+(?=\\\\$\\n))'
-				end: '(?<!")(?=[ \t])|(?:[^\\s"\\\\]|""|\\\\(?!").)*(?:(?<!\\\\)(?:"|(?=$))|(?=\\\\"))'
-				beginCaptures: 0: name: "markup.plain.roff"
-				endCaptures:   0: name: "markup.italic.roff"
-				patterns: [{
-					name: "markup.plain.roff"
-					begin: "(?<=\\G)\\\\$\\n"
-					end:   '(?<!\\\\)"(?!")|(?<!\\\\)$|(?=\\\\")'
-					patterns: [
-						{include: "#string-escapes"}
-						{begin: '^(?:[^"\\\\]|""|\\\\(?!").)*\\\\$\\n', end: "^"}
-					]}
-					{name: "markup.plain.roff", match: '^(?:[^\\\\"]|""|\\\\[^"])+(?<!\\\\)"'}
-					{name: "markup.italic.roff", begin: '[ \\t]+(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\[^"])+\\\\$\\n', end: '(?<!\\\\)$|(?="[ \\t])|(?=\\\\")|(?<!\\\\)(?=[ \\t])'}
-					{name: "markup.italic.roff", match: '(?:[ \\t]+|(?<=")(?=[^"]|$))(?!\\\\?")(?:[^\\s"\\\\]|""|\\\\(?!").)+'}
-					{name: "markup.plain.roff", begin: "\\\\$\\n", end: "^"}
-					{name: "markup.italic.roff", match: '^(?:[^"\\s\\\\]|""|\\\\(?!").)+'}]
-			},{
-				match: '(?<!\\\\)("(?:[^\\\\"]|""|\\\\(?!").)*")[ \t]+("(?:[^\\\\"]|""|\\\\(?!").)*(?:(?=[^"\\\\]$)|"))'
-				captures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.italic.roff"
-			},{
-				begin: '(?<!\\\\)("(?:[^"]|"")*"|(?:[^\\s\\\\]|\\\\.)+)[ \t]+("(?:[^"]|"")*\\\\$\\n)'
-				patterns: [
-					{name: "markup.italic.roff", match: '^(?:[^"]|"")*\\\\$\\n'}
-					{include: "#string-escapes"}
-				]
-				end: '$|(?:[^"\\\\]|""|\\\\(?!").)*(?:(?=\\\\")|\\s*"(?=$|[^"]))'
-				beginCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.italic.roff"
-				contentName: "markup.italic.roff"
-				endCaptures:
-					0: name: "markup.italic.roff"
-			},{
-				begin: '(?<=\\s")(?:[^"\\\\]|""|\\\\(?=$)|\\\\(?!").)+'
-				end:   '(?=\\\\")|(?<!\\\\)(?=$)|"[ \\t]+'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.italic.roff"
-				beginCaptures: 0: name: "markup.italic.roff"
-				endCaptures:   0: name: "markup.italic.roff"
-			},{
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end:   '(?=\\\\")|^(?=(?:[^\\s\\\\]|\\\\[^\"])+[ \t]+)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=$\\n)|^((?:[^\\s\\\\]|\\\\[^\"])+)(?=[ \t]*$\\n)|^[ \t]+((?:[^ \t\\\\]|\\\\[^\"])+)(?=[ \t]|$)|^(?=$\\n)'
-				contentName: "markup.plain.roff"
-				beginCaptures:
-					0: name: "markup.plain.roff"
-				endCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.plain.roff"
-					3: name: "markup.italic.roff"
-			},{
-
-				begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\$\\n'
-				end: '(?=[ \t]+(?:[^\\s\\\\]|\\\\[^\"])+(?:\\\\(?=\\n)|[ \t]+|\\Z))'
-				beginCaptures: 0: name: "markup.plain.roff"
-				endCaptures:   1: name: "markup.italic.roff"
-				patterns: [{
-					begin: '^(\\S+)[ \t]+(\\S+\\\\$\\n)'
-					end: '(?=[ \t])|(?<!\\\\)(?=\\n)'
-					contentName: "markup.italic.roff"
-					beginCaptures:
-						1: name: "markup.plain.roff"
-						2: name: "markup.italic.roff"
-				},{
-					contentName: "markup.plain.roff"
-					begin: "^(?=\\S)"
-					end: '([ \t]+\\S+\\\\\\$\\n)|([ \t]+\\S+)(?=[ \t]|(?<!\\\\)\\Z)'
-					endCaptures:
-						1: name: "markup.italic.roff"
-						2: name: "markup.italic.roff"
-				}]
-			},{
-				begin: '((?!")(?:[^\\s\\\\]|\\\\[^\"])+)[ \t]+(?=(?:[^\\s\\\\]|\\\\[^\"])+$)'
-				contentName: "markup.italic.roff"
-				end: '^(?=[ \t]|$\\n|\\\\\")|((?:[^\\s\\\\]|\\\\(?!").)+)(?=\\\\\"|$|[ \t])'
-				beginCaptures: 1: name: "markup.plain.roff"
-				endCaptures:   0: name: "markup.italic.roff"
-				patterns: [{
-					name: "markup.italic.roff"
-					begin: '(?:[^\\s\\\\]|\\\\[^\"])+\\\\(?=$)'
-					end: "(?<=\\S)(?=[ \t]|$)|^(?=[ \t])"
-					beginCaptures: 0: name: "markup.italic.roff"
-				},{
-					name: "markup.italic.roff"
-					begin: "^\\S+"
-					end: "(?=[ \t]|$\\n)"
-				}]
-			},{
-				begin: '(?:(?<=[ \t])|\\G)((?:[^\\s\\\\]|\\\\[^\"])+)(?:$|[ \t]+\\\\$\\n)'
-				end: '(?=[ \t])|(?=(?:[^\\s\\\\]|\\\\[^\"])*$)'
-				contentName: "markup.italic.roff"
-				beginCaptures: 1: name: "markup.plain.roff"
-			},{
-				name: "markup.plain.roff"
-				match: '"(?:[^"\\\\]|""|\\\\(?!").)+(?=\\\\")'
-			},{
-				begin: '"'
-				end: '(?=\\\\")|(?<!\\\\)$|(")([ \\t]*(?!")(?:[^\\s\\\\]|\\\\(?!").)+)'
-				contentName: "markup.plain.roff"
-				patterns: [include: "#string-escapes"]
-				beginCaptures: 0: name: "markup.plain.roff"
-				endCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.italic.roff"
-			},{
-				begin: '(?!\\\\")(?=\\S)'
-				end: '(?=\\\\\")|(?:((?:[^"\\s\\\\]|""|\\\\(?!").)*)[ \t]+((?:[^"\\s\\\\]|""|\\\\(?!").)+)[ \t]*)|(?=$)'
-				patterns: [begin: "\\\\$\\n", end: "^"]
-				contentName: "markup.plain.roff"
-				endCaptures:
-					1: name: "markup.plain.roff"
-					2: name: "markup.italic.roff"
+				match: '((?:[^"\\\\]|""|\\\\(?!").)+)(?!$)'
+				captures: 1: patterns: [include: "#string-escapes"]
+			}, include: "#string-escapes"]
+		},{
+			name: "markup.bold.roff"
+			begin: '[ \t]+(\\\\$\\n?)'
+			end:   '(?<!^)[ \t]+|(?=\\\\")|(?<!\\\\)(?=\\n|$)'
+			beginCaptures: 1: patterns: [include: "#escapes"]
+			patterns: [include: "#escapes", {
+				begin: "^[ \t]+"
+				end: "(?=\\S)|(?<!\\\\)(?:$|\\n)"
 			}]
-
-		}]
+		},{
+			name: "markup.bold.roff"
+			begin: '[ \t]+(?!")((?:[^\\s"\\\\]|\\\\(?!").)+)'
+			end:   '[ \t]+|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
+			beginCaptures: 1: patterns: [include: "#escapes"]
+			patterns: [include: "#escapes"]
+		}]
+
+
+	"odd-italic":
+		patterns: [{
+			name: "markup.italic.roff"
+			begin: '[ \t]+(")'
+			end:   '(")[ \t]*|(?=\\\\")|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)'
+			beginCaptures: 1: name: "punctuation.definition.string.begin.roff"
+			endCaptures:   1: name: "punctuation.definition.string.end.roff"
+			patterns: [{
+				match: '((?:[^"\\\\]|""|\\\\(?!").)+)(?!$)'
+				captures: 1: patterns: [include: "#string-escapes"]
+			}, include: "#string-escapes"]
+		},{
+			name: "markup.italic.roff"
+			begin: '[ \t]+(\\\\$\\n?)'
+			end:   '(?<!^)[ \t]+|(?=\\\\")|(?<!\\\\)(?=\\n|$)'
+			beginCaptures: 1: patterns: [include: "#escapes"]
+			patterns: [include: "#escapes", {
+				begin: "^[ \t]+"
+				end: "(?=\\S)|(?<!\\\\)(?:$|\\n)"
+			}]
+		},{
+			name: "markup.italic.roff"
+			begin: '[ \t]+(?!")((?:[^\\s"\\\\]|\\\\(?!").)+)'
+			end:   '[ \t]+|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
+			beginCaptures: 1: patterns: [include: "#escapes"]
+			patterns: [include: "#escapes"]
+		}]
+
+
+	"odd-roman":
+		patterns: [{
+			name: "markup.plain.roff"
+			begin: '[ \t]+(")'
+			end:   '(")[ \t]*|(?=\\\\")|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)'
+			beginCaptures: 1: name: "punctuation.definition.string.begin.roff"
+			endCaptures:   1: name: "punctuation.definition.string.end.roff"
+			patterns: [{
+				match: '((?:[^"\\\\]|""|\\\\(?!").)+)(?!$)'
+				captures: 1: patterns: [include: "#string-escapes"]
+			}, include: "#string-escapes"]
+		},{
+			name: "markup.plain.roff"
+			begin: '[ \t]+(\\\\$\\n?)'
+			end:   '(?<!^)[ \t]+|(?=\\\\")|(?<!\\\\)(?=\\n|$)'
+			beginCaptures: 1: patterns: [include: "#escapes"]
+			patterns: [include: "#escapes", {
+				begin: "^[ \t]+"
+				end: "(?=\\S)|(?<!\\\\)(?:$|\\n)"
+			}]
+		},{
+			name: "markup.plain.roff"
+			begin: '[ \t]+(?!")((?:[^\\s"\\\\]|\\\\(?!").)+)'
+			end:   '[ \t]+|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
+			beginCaptures: 1: patterns: [include: "#escapes"]
+			patterns: [include: "#escapes"]
+		}]
+
+
+
+	"even-bold":
+		patterns: [
+			name: "markup.bold.roff"
+			begin: '(?<=^|\\s|")(?!"|\\\\")((?:[^\\s"\\\\]|\\\\(?!").)+)'
+			end:   '(?=[ \t])|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
+			beginCaptures: 1: patterns: [include: "#escapes"]
+			patterns: [include: "#escapes"]
+		]
+
+	"even-italic":
+		patterns: [
+			name: "markup.italic.roff"
+			begin: '(?<=^|\\s|")(?!"|\\\\")((?:[^\\s"\\\\]|\\\\(?!").)+)'
+			end:   '(?=[ \t])|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
+			beginCaptures: 1: patterns: [include: "#escapes"]
+			patterns: [include: "#escapes"]
+		]
+
+	"even-roman":
+		patterns: [
+			name: "markup.plain.roff"
+			begin: '(?<=^|\\s|")(?!"|\\\\")((?:[^\\s"\\\\]|\\\\(?!").)+)'
+			end:   '(?=[ \t])|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)|(?=\\\\")'
+			beginCaptures: 1: patterns: [include: "#escapes"]
+			patterns: [include: "#escapes"]
+		]
+
+
+	"even-bold-after-italic":
+		patterns: [{
+			contentName: "markup.bold.roff"
+			begin: '(")'
+			end:   '(("))([^"\\s]+[ \t]*)?|(?=\\\\")|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)'
+			beginCaptures:
+				0: name: "markup.bold.roff"
+				1: name: "punctuation.definition.string.begin.roff"
+			endCaptures:
+				1: name: "markup.bold.roff"
+				2: name: "punctuation.definition.string.end.roff"
+				3: name: "markup.italic.roff"
+			patterns: [{
+				match: '((?:[^"\\\\]|""|\\\\(?!").)+)(?!$)'
+				captures: 1: patterns: [include: "#string-escapes"]
+			}, include: "#string-escapes"]
+		}]
+
+	"even-bold-after-roman":
+		patterns: [{
+			contentName: "markup.bold.roff"
+			begin: '(")'
+			end:   '(("))([^"\\s]+[ \t]*)?|(?=\\\\")|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)'
+			beginCaptures:
+				0: name: "markup.bold.roff"
+				1: name: "punctuation.definition.string.begin.roff"
+			endCaptures:
+				1: name: "markup.bold.roff"
+				2: name: "punctuation.definition.string.end.roff"
+				3: name: "markup.plain.roff"
+			patterns: [{
+				match: '((?:[^"\\\\]|""|\\\\(?!").)+)(?!$)'
+				captures: 1: patterns: [include: "#string-escapes"]
+			}, include: "#string-escapes"]
+		}]
+
+
+	"even-italic-after-bold":
+		patterns: [{
+			contentName: "markup.italic.roff"
+			begin: '(")'
+			end:   '(("))([^"\\s]+[ \t]*)?|(?=\\\\")|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)'
+			beginCaptures:
+				0: name: "markup.italic.roff"
+				1: name: "punctuation.definition.string.begin.roff"
+			endCaptures:
+				1: name: "markup.italic.roff"
+				2: name: "punctuation.definition.string.end.roff"
+				3: name: "markup.bold.roff"
+			patterns: [{
+				match: '((?:[^"\\\\]|""|\\\\(?!").)+)(?!$)'
+				captures: 1: patterns: [include: "#string-escapes"]
+			}, include: "#string-escapes"]
+		}]
+
+	"even-italic-after-roman":
+		patterns: [{
+			contentName: "markup.italic.roff"
+			begin: '(")'
+			end:   '(("))([^"\\s]+[ \t]*)?|(?=\\\\")|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)'
+			beginCaptures:
+				0: name: "markup.italic.roff"
+				1: name: "punctuation.definition.string.begin.roff"
+			endCaptures:
+				1: name: "markup.italic.roff"
+				2: name: "punctuation.definition.string.end.roff"
+				3: name: "markup.plain.roff"
+			patterns: [{
+				match: '((?:[^"\\\\]|""|\\\\(?!").)+)(?!$)'
+				captures: 1: patterns: [include: "#string-escapes"]
+			}, include: "#string-escapes"]
+		}]
+
+
+	"even-roman-after-bold":
+		patterns: [{
+			contentName: "markup.plain.roff"
+			begin: '(")'
+			end:   '(("))([^"\\s]+[ \t]*)?|(?=\\\\")|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)'
+			beginCaptures:
+				0: name: "markup.plain.roff"
+				1: name: "punctuation.definition.string.begin.roff"
+			endCaptures:
+				1: name: "markup.plain.roff"
+				2: name: "punctuation.definition.string.end.roff"
+				3: name: "markup.bold.roff"
+			patterns: [{
+				match: '((?:[^"\\\\]|""|\\\\(?!").)+)(?!$)'
+				captures: 1: patterns: [include: "#string-escapes"]
+			}, include: "#string-escapes"]
+		}]
+
+	"even-roman-after-italic":
+		patterns: [{
+			contentName: "markup.plain.roff"
+			begin: '(")'
+			end:   '(("))([^"\\s]+[ \t]*)?|(?=\\\\")|(?<![^\\\\]\\\\|^\\\\)(?=\\n|$)'
+			beginCaptures:
+				0: name: "markup.plain.roff"
+				1: name: "punctuation.definition.string.begin.roff"
+			endCaptures:
+				1: name: "markup.plain.roff"
+				2: name: "punctuation.definition.string.end.roff"
+				3: name: "markup.italic.roff"
+			patterns: [{
+				match: '((?:[^"\\\\]|""|\\\\(?!").)+)(?!$)'
+				captures: 1: patterns: [include: "#string-escapes"]
+			}, include: "#string-escapes"]
+		}]


Refactor and reorganize remaining unsorted keywords

--- grammars/rust.cson
+++ grammars/rust.cson
@@ -337,6 +337,38 @@

   # Keywords

+  ## Modifiers
+
+  'modifiers': {
+    'patterns': [
+      {
+        'comment': 'Mutable storage modifier'
+        'name': 'storage.modifier.mut.rust'
+        'match': '\\bmut\\b'
+      }
+      {
+        'comment': 'Visibility modifier'
+        'name': 'storage.modifier.visibility.rust'
+        'match': '\\bpub\\b'
+      }
+      {
+        'comment': 'Const storage modifier'
+        'name': 'storage.modifier.const.rust'
+        'match': '\\bconst\\b'
+      }
+      {
+        'comment': 'Static storage modifier'
+        'name': 'storage.modifier.static.rust'
+        'match': '\\bstatic\\b'
+      }
+      {
+        'comment': 'Unsafe code keyword'
+        'name': 'keyword.other.unsafe.rust'
+        'match': '\\bunsafe\\b'
+      }
+    ]
+  }
+
   ## Types

   'types': {
@@ -431,15 +463,28 @@
     ]
   }

-  ## Structs
-
-  'enum_varients': {
-    'patterns': [
-      {
-        'comment': 'Core struct'
+  ## Special variables
+
+  'special_enums': {
+    'patterns': [
+      {
+        'comment': 'Enum'
         'name': 'support.constant.core.rust'
         'match': '\\b(Some|None|Ok|Err)\\b'
       }
+    ]
+  }
+
+  'self': {
+    'comment': 'Self variable'
+    'name': 'variable.language.rust'
+    'match': '\\bself\\b'
+  }
+
+  'special_variables': {
+    'patterns': [
+      { 'include': '#special_enums' }
+      { 'include': '#self' }
     ]
   }

@@ -479,6 +524,11 @@
         'name': 'invalid.illegal.rust'
         'match': '\\+\\+|--'
       }
+      {
+        'comment': 'Box pattern'
+        'name': 'storage.modifier.box.rust'
+        'match': '\\bbox\\b'
+      }
     ]
   }

@@ -510,59 +560,46 @@
     ]
   }

-  # Unsorted
+  # Grammar structures

   'sigils': {
     'comment': 'Sigil'
     'name': 'keyword.operator.sigil.rust'
     'match': '[&*](?=[\\w\\(\\[\\|\\"]+)'
   }
-  'self': {
-    'comment': 'Self variable'
-    'name': 'variable.language.rust'
-    'match': '\\bself\\b'
-  }
-  'mut': {
-    'comment': 'Mutable storage modifier'
-    'name': 'storage.modifier.mut.rust'
-    'match': '\\bmut\\b'
-  }
-  'box': {
-    'comment': 'Box storage modifier'
-    'name': 'storage.modifier.box.rust'
-    'match': '\\bbox\\b'
-  }
-  'const': {
-    'comment': 'Const storage modifier'
-    'name': 'storage.modifier.const.rust'
-    'match': '\\bconst\\b'
-  }
-  'pub': {
-    'comment': 'Visibility modifier'
-    'name': 'storage.modifier.visibility.rust'
-    'match': '\\bpub\\b'
-  }
-  'unsafe': {
-    'comment': 'Unsafe code keyword'
-    'name': 'keyword.other.unsafe.rust'
-    'match': '\\bunsafe\\b'
-  }
-  'where': {
-    'comment': 'Generic where clause'
-    'begin': '\\b(where)\\b'
-    'end': '(?=\\{)'
-    'beginCaptures': {
-      '1': { 'name': 'keyword.other.where.rust' }
-    }
-    'patterns': [
-      { 'include': '#comments' }
-      { 'include': '#structure_operators' }
-      { 'include': '#types' }
-      { 'include': '#traits' }
-      { 'include': '#type_params' }
-      { 'include': '#lifetime' }
-    ]
-  }
+
+  'where': {
+    'comment': 'Generic where clause'
+    'begin': '\\b(where)\\b'
+    'end': '(?=\\{)'
+    'beginCaptures': {
+      '1': { 'name': 'keyword.other.where.rust' }
+    }
+    'patterns': [
+      { 'include': '#comments' }
+      { 'include': '#structure_operators' }
+      { 'include': '#types' }
+      { 'include': '#traits' }
+      { 'include': '#type_params' }
+      { 'include': '#lifetime' }
+    ]
+  }
+
+  'type_params': {
+    'comment': 'Type parameters'
+    'name': 'meta.type_params.rust'
+    'begin': '<(?![=<])'
+    'end': '>'
+    'patterns': [
+      { 'include': '#comments' }
+      { 'include': '#sigils' }
+      { 'include': '#modifiers' }
+      { 'include': '#lifetime' }
+      { 'include': '#types' }
+      { 'include': '#traits' }
+      { 'include': '#type_params' }
+    ]
+  }

   'lifetime': {
     'comment': 'Named lifetime'
@@ -572,6 +609,7 @@
       '1': { 'name': 'entity.name.lifetime.rust' }
     }
   }
+
   'ref_lifetime': {
     'comment': 'Reference with named lifetime'
     'match': '&(\'([\\p{Alpha}_][\\w]*))\\b'
@@ -580,23 +618,7 @@
       '2': { 'name': 'entity.name.lifetime.rust' }
     }
   }
-  'type_params': {
-    'comment': 'Type parameters'
-    'name': 'meta.type_params.rust'
-    'begin': '<(?![=<])'
-    'end': '>'
-    'patterns': [
-      { 'include': '#comments' }
-      { 'include': '#sigils' }
-      { 'include': '#mut' }
-      { 'include': '#lifetime' }
-      { 'include': '#types' }
-      { 'include': '#traits' }
-      { 'include': '#type_params' }
-    ]
-  }
-}
-
+
 'patterns': [
   # Comments
   { 'include': '#comments' }
@@ -621,11 +643,6 @@

   # Language
   {
-    'comment': 'Static storage modifier'
-    'name': 'storage.modifier.static.rust'
-    'match': '\\bstatic\\b'
-  }
-  {
     'comment': 'Control keyword'
     'name': 'keyword.control.rust'
     'match': '\\b(break|continue|else|if|in|for|loop|match|return|while)\\b'
@@ -635,15 +652,10 @@
     'name': 'keyword.other.rust'
     'match': '\\b(crate|extern|mod|let|proc|ref|use|super|as|move)\\b'
   }
-  { 'include': '#unsafe' }
   { 'include': '#sigils' }
-  { 'include': '#self' }
-  { 'include': '#mut' }
-  { 'include': '#box' }
+  { 'include': '#modifiers' }
   { 'include': '#lifetime' }
   { 'include': '#ref_lifetime' }
-  { 'include': '#const' }
-  { 'include': '#pub' }

   # Operators
   { 'include': '#operators' }
@@ -651,14 +663,13 @@

   # Standard types and traits
   { 'include': '#types' }
-  { 'include': '#enum_varients' }
+  { 'include': '#special_variables' }
   { 'include': '#traits' }
   { 'include': '#macros' }
-  # TODO: procs and closures

   # Function and macro calls
   {
-    'comment': 'Invokation of a macro'
+    'comment': 'Macro invocation'
     'match': '\\b([\\p{Alpha}_][\\w]*\\!)'
     'captures': {
       '1': { 'name': 'entity.name.function.macro.rust' }
@@ -691,16 +702,14 @@
         'patterns': [
           { 'include': '#comments' }
           { 'include': '#structure_operators' }
+          { 'include': '#special_variables' }
           { 'include': '#sigils' }
-          { 'include': '#self' }
-          { 'include': '#mut' }
+          { 'include': '#modifiers' }
           { 'include': '#ref_lifetime' }
           { 'include': '#types' }
           { 'include': '#traits' }
           { 'include': '#type_params' }
-          { 'include': '#const' }
           { 'include': '#where' }
-          { 'include': '#unsafe' }
           {
             'comment': 'Function arguments'
             'match': 'fn'
@@ -730,10 +739,12 @@
         'patterns': [
           { 'include': '#comments' }
           { 'include': '#structure_operators' }
+          { 'include': '#sigils' }
+          { 'include': '#modifiers' }
           { 'include': '#traits' }
           { 'include': '#type_params' }
           { 'include': '#types' }
-          { 'include': '#pub' }
+          { 'include': '#modifiers' }
           { 'include': '#where' }
         ]
       }
@@ -758,10 +769,11 @@
           '1': { 'name': 'entity.name.type.rust' }
         }
         'patterns': [
+          { 'include': '#comments' }
           { 'include': '#punctuation' }
           { 'include': '#operators' }
           { 'include': '#sigils' }
-          { 'include': '#mut' }
+          { 'include': '#modifiers' }
           { 'include': '#lifetime' }
           { 'include': '#ref_lifetime' }
           { 'include': '#types' }


Fix request response

Fix request response

--- grammars/sifu.cson
+++ grammars/sifu.cson
@@ -802,6 +802,18 @@
             'include': '#queryInlineCount'
           }
           {
+            'include': '#requestInlineDto'
+          }
+          {
+            'include': '#requestDto'
+          }
+          {
+            'include': '#responseInlineDto'
+          }
+          {
+            'include': '#responseDto'
+          }
+          {
             'include': '#secured'
           }
           {
@@ -861,10 +873,10 @@
             'include': '#responseDto'
           }
           {
+            'include': '#responseMapped'
+          }
+          {
             'include': '#stub'
-          }
-          {
-            'include': '#responseMapped'
           }
           {
             'include': '#secured'
@@ -2098,10 +2110,22 @@
             'include': '#endpointQueryCount'
           }
           {
+            'include': '#queryInlineDelete'
+          }
+          {
             'include': '#endpointQueryDelete'
           }
           {
-            'include': '#queryInlineDelete'
+            'include': '#requestInlineDto'
+          }
+          {
+            'include': '#requestDto'
+          }
+          {
+            'include': '#responseInlineDto'
+          }
+          {
+            'include': '#responseDto'
           }
           {
             'include': '#responseMapped'
@@ -3545,6 +3569,18 @@
             'include': '#queryInlineFind'
           }
           {
+            'include': '#requestInlineDto'
+          }
+          {
+            'include': '#requestDto'
+          }
+          {
+            'include': '#responseInlineDto'
+          }
+          {
+            'include': '#responseDto'
+          }
+          {
             'include': '#responseMapped'
           }
           {
@@ -7990,6 +8026,18 @@
           }
           {
             'include': '#endpointQueryCount'
+          }
+          {
+            'include': '#requestInlineDto'
+          }
+          {
+            'include': '#requestDto'
+          }
+          {
+            'include': '#responseInlineDto'
+          }
+          {
+            'include': '#responseDto'
           }
           {
             'include': '#responseMapped'
@@ -9051,13 +9099,25 @@
             'include': '#queryInlineGet'
           }
           {
+            'include': '#queryInlineSelect'
+          }
+          {
+            'include': '#endpointQuerySelect'
+          }
+          {
+            'include': '#requestInlineDto'
+          }
+          {
+            'include': '#requestDto'
+          }
+          {
+            'include': '#responseInlineDto'
+          }
+          {
+            'include': '#responseDto'
+          }
+          {
             'include': '#responseMapped'
-          }
-          {
-            'include': '#queryInlineSelect'
-          }
-          {
-            'include': '#endpointQuerySelect'
           }
           {
             'include': '#secured'
@@ -11790,10 +11850,10 @@
             'include': '#responseDto'
           }
           {
+            'include': '#responseMapped'
+          }
+          {
             'include': '#stub'
-          }
-          {
-            'include': '#responseMapped'
           }
           {
             'include': '#secured'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -135,7 +135,7 @@
     height - Height of the meter
     position - An x, y object representing the position of the meter
     radius - Border radius of the meter
-    showName - Boolean to toggle whether or not to show the attribute associated with the
+    showName - Boolean to toggle whether or not to show the attribute associated with the meter
   ###
   meter: (options={}) ->
     Object.reverseMerge options,


More informative loadLibrary error message.

--- src/methods.coffee
+++ src/methods.coffee
@@ -57,7 +57,10 @@

 trapped_methods = {
   'java/lang/System::setJavaLangAccess()V': (rs) -> #NOP
-  'java/lang/System::loadLibrary(Ljava/lang/String;)V': (rs) -> error "warning: library loads are NYI"
+  'java/lang/System::loadLibrary(Ljava/lang/String;)V': (rs) ->
+      args = rs.curr_frame().locals
+      lib = rs.jvm2js_str rs.get_obj args[0]
+      error "Attempt to load library '#{lib}' failed: library loads are NYI"
   'java/lang/System::adjustPropertiesForBackwardCompatibility(Ljava/util/Properties;)V': (rs) -> #NOP (apple-java specific?)
   'java/lang/ThreadLocal::<clinit>()V': (rs) -> #NOP
   'java/lang/ThreadLocal::<init>()V': (rs) -> #NOP


rm more unnecessary traps

rm more unnecessary traps

--- src/methods.coffee
+++ src/methods.coffee
@@ -63,23 +63,15 @@
         SoftReference: [
           o 'get()Ljava/lang/Object;', (rs) -> null
         ]
-      #Class: [
-      #  o 'newInstance0()L!/!/Object;', (rs, _this) -> #implemented here to avoid reflection
-      #      classname = _this.fields.$type.toClassString()
-      #      rs.push (oref = rs.init_object(classname))
-      #      rs.method_lookup({'class':classname,'sig':{'name':'<init>'}}).run(rs)
-      #      oref
-      #]
       Object: [
         o '<clinit>()V', (rs) -> # NOP, for efficiency reasons
       ]
       System: [
-        o 'setJavaLangAccess()V', (rs) -> # NOP
-        o 'loadLibrary(L!/!/String;)V', (rs, jvm_str) -> # NOP
-        o 'adjustPropertiesForBackwardCompatibility(L!/util/Properties;)V', (rs) -> #NOP (apple-java specific?)
+        o 'loadLibrary(L!/!/String;)V', (rs) -> # NOP, because we don't support loading external libraries
+        o 'adjustPropertiesForBackwardCompatibility(L!/util/Properties;)V', (rs) -> # NOP (apple-java specific)
       ]
       Terminator: [
-        o 'setup()V', (rs) -> #NOP
+        o 'setup()V', (rs) -> # NOP, because we don't support threads
       ]
       Throwable: [
         o 'printStackTrace(L!/io/PrintWriter;)V', (rs) -> # NOP, since we didn't fill in anything
@@ -110,9 +102,8 @@
         o 'getInstance(Ljava/lang/String;)Ljava/util/Currency;', (rs) -> null # because it uses lots of reflection and we don't need it
       ]
       ResourceBundle: [
-        o 'getBundle(L!/lang/String;L!/!/Locale;L!/!/ResourceBundle$Control;)L!/!/!;', getBundle
-        o 'getBundle(L!/lang/String;)L!/!/!;', getBundle
-        o 'getLoader()L!/lang/ClassLoader;', (rs) -> rs.init_object 'java/lang/ClassLoader' # mock
+        o 'getBundleImpl(L!/lang/String;L!/!/Locale;L!/lang/ClassLoader;L!/!/!$Control;)L!/!/!;', getBundle
+        o 'getLoader()L!/lang/ClassLoader;', (rs) -> null
       ]
       EnumSet: [
         o 'getUniverse(L!/lang/Class;)[L!/lang/Enum;', (rs) ->


Changed testpie colors

Changed testpie colors


added missing err to fallback done callback

--- tools/pubsub.coffee
+++ tools/pubsub.coffee
@@ -29,7 +29,7 @@
     ##
     ##
     publish: (topic, data, done)->
-        done ?= ()=>
+        done ?= (err)=>
             @_ctx.logger.error(err) if err

         @_getBroker (err, broker)=>


removing extra reset trigger

removing extra reset trigger

--- source/javascripts/src/view_xlformbuilder.coffee
+++ source/javascripts/src/view_xlformbuilder.coffee
@@ -185,7 +185,6 @@
   addRowAtIndex: (opts={}, atIndex=false)->
     atIndex = @survey.rows.length  if atIndex is false
     @survey.rows.add(new XLF.Row(opts), at: atIndex)
-    @survey.rows.trigger("reset")
   publishButtonClick: (evt)->
     @onPublish.call(@, arguments)


Add RowMap::inspect, which returns a string for debugging the regions

--- src/row-map.coffee
+++ src/row-map.coffee
@@ -174,3 +174,6 @@
       bufferRow += region.bufferRows
       screenRow += region.screenRows
     { index, screenRow, bufferRow }
+
+  inspect: ->
+    @regions.map(({screenRows, bufferRows}) -> "#{screenRows}:#{bufferRows}").join(', ')


removed unused module

removed unused module

--- build/watcher.coffee
+++ build/watcher.coffee
@@ -24,8 +24,6 @@
   eslint = require('gulp-eslint')
   compileScripts = require('./scripts')
   compileStyles = require('./styles')
-
-  stylint = require('gulp-stylint')

   nodemonRestarts = 0


Update /roll and begin implementation of /ore

--- src/rpgdice.coffee
+++ src/rpgdice.coffee
@@ -24,9 +24,43 @@
     else
       results = []
       results.push(randint sides) for [1..num]
-      return results.toString()
+      return results

+  # Basic die roller
   robot.respond /roll (?:([0-9]+)d([0-9]+))(?: (.*))*/i, (msg) ->
     num = msg.match[1]
     sides = msg.match[2]
-    msg.send(rolldice sides, num)
+    rolls = rolldice sides, num
+    rolls.sort()
+    rollsTotal = 0
+    rollsTotal += i for i in rolls
+    msg.send("You rolled #{rolls} for a total of #{rollsTotal}")
+
+  robot.respond /ore (\d+)( \d+)?( \d+)?( .+)?/im, (msg) ->
+    ### <number of dice> [<called>] [<expert>] [<note>] ###
+
+    num = msg.match[1]
+    if num > 10 or num < 1
+      return msg.send "You must roll between 1 and 10 dice"
+    rolls = rolldice(10,num)
+
+    note = called = expert = ""
+
+    called = parseInt(msg.match[2])
+    if called isnt NaN
+      if called > 10 or called < 1
+        return msg.send "They're d10s, you idiot. You can't call a side that doesn't exist."
+      else
+        rolls.push called
+
+    note = msg.match[4] if msg.match[4]
+
+    expert = parseInt(msg.match[3])
+    if expert isnt NaN
+      if expert > 10 or expert < 1
+        return msg.send "For someone with \"expert\" dice you sure aren't an expert at basic math. Expert dice have 10 sides."
+      else
+        rolls.push expert
+
+    rolls.sort()
+    msg.send "num: #{num}, note: #{note}, called: #{called}, expert: #{expert}, rolls: #{rolls}"


skip empty score documents

skip empty score documents

--- src/service.coffee
+++ src/service.coffee
@@ -221,14 +221,16 @@
           docs.forEach (doc) ->
             if doc.shortfile isnt pastFile
               dailyData.push score if score isnt null
-              score =
-                lang: doc.lang
-                file: doc.shortfile
-                convention: doc.convention
-
-              pastFile = doc.shortfile
+              if Object.keys(doc.convention).length > 1
+                score =
+                  lang: doc.lang
+                  file: doc.shortfile
+                  convention: doc.convention
+
+                pastFile = doc.shortfile
             else
-              merge score, doc
+              if Object.keys(doc.convention).length > 1
+                merge score, doc
           dailyData.push score

           sumData =


removed a redundant line

removed a redundant line

--- src/twitter.coffee
+++ src/twitter.coffee
@@ -125,7 +125,6 @@
     constructor: (screenname, options) ->
         @user = new TwitterUserTimeline screenname, options
         @search = new TwitterSearchTimeline "to:" + screenname
-        @subscriber = new Chorus.PubSub()
         @subscribe @user
         @subscribe @search


Added invulnerability period to shark attack game

--- src/documents/games/sharkattack/script.js.coffee
+++ src/documents/games/sharkattack/script.js.coffee
@@ -40,6 +40,7 @@
     @damage = 0
     @safe = false
     @crashed = false
+    @invulnerable = false
     super(x, y, 0, -1*@forwardSpeed)

   updatePosition: (goLeft, goRight) ->
@@ -71,9 +72,13 @@
     ]

   takeBite: ->
-    @damage++
-    if @damage==@hp
-      throw 'eaten'
+    unless @invulnerable
+      @damage++
+      if @damage==@hp
+        throw 'eaten'
+      else
+        @invulnerable = true
+        setTimeout((=> @invulnerable = false), 400)

 angular.module('sharkAttackApp', []).controller('SharkAttackCtrl', ['$scope', ($scope) ->


Tabs

Tabs

- Renamed cui-tabs-header* to cui-tabs-pane-header.

--- src/elements/Tabs/Tabs.coffee
+++ src/elements/Tabs/Tabs.coffee
@@ -58,10 +58,10 @@
 		@__tabs_bodies = new Template
 			name: "tabs-bodies"

-		@__pane_header.addClass("cui-tabs-header")
+		@__pane_header.addClass("cui-tabs-pane-header")

 		if @_appearance == "mini"
-			@__pane_header.addClass("cui-tabs-header--mini")
+			@__pane_header.addClass("cui-tabs-pane-header--mini")

 		@__buttonbar = new Buttonbar()


GameObject.Effect

GameObject.Effect

--- src/game_object_effect.coffee
+++ src/game_object_effect.coffee
@@ -8,7 +8,7 @@
       # this will fade the player object out over the next 2 seconds.
       player.fadeOut 2

-
+      #

   @name fadeOut
   @methodOf GameObject#


GameObject.Effect

GameObject.Effect

--- src/game_object_effect.coffee
+++ src/game_object_effect.coffee
@@ -9,6 +9,7 @@
       player.fadeOut 2

       # Fade out and then destroy
+      player.fade

   @name fadeOut
   @methodOf GameObject#


GameObject.Effect

GameObject.Effect

--- src/game_object_effect.coffee
+++ src/game_object_effect.coffee
@@ -3,7 +3,7 @@
 You may also provide a callback function that is executed when the sprite has finished fading out.

 @name Effect
-@field
+@fieldOf GameObject
 @module
 @constructor
 @param {Object} I Instance variables


prevented expansion of triple quote

prevented expansion of triple quote

see https://github.com/codemirror/CodeMirror/pull/3083

--- client/editor.coffee
+++ client/editor.coffee
@@ -104,7 +104,7 @@

   cm = CodeMirror $e.find('.cm')[0],
     lineNumbers: !opts.debugger, fixedGutter: false, firstLineNumber: 0, lineNumberFormatter: (i) -> "[#{i}]"
-    keyMap: 'dyalog', matchBrackets: true, autoCloseBrackets: true, gutters: ['breakpoints', 'CodeMirror-linenumbers']
+    keyMap: 'dyalog', matchBrackets: true, autoCloseBrackets: {triples: ''}, gutters: ['breakpoints', 'CodeMirror-linenumbers']
     extraKeys: k

   createBreakpointElement = -> $('
●
')[0]


fix error msg

fix error msg

--- client/client.coffee
+++ client/client.coffee
@@ -10,7 +10,10 @@
 		Meteor.call "createQuestion", {
 			question: question
 		}, (error, question) ->
-			if error then Session.set("createQuestionError", error.reason)
+			if error
+				Session.set("createQuestionError", error.reason)
+			else
+				Session.set("createQuestionError", null)

 Template.createQuestion.error = ->
 	Session.get "createQuestionError"


made toolbar code more concise

made toolbar code more concise

--- client/editor.coffee
+++ client/editor.coffee
@@ -1,41 +1,49 @@
 Dyalog.Editor = (e, opts = {}) ->
-  ($e = $ e).html '''
+  b = (name, description) -> ""
+  ($e = $ e).html """

-
-
-
-
-
-
-
-
-
-
-
-
-
-      <input class="search text-field">
-      ▶
-      ◀
-      aA
+      #{[
+        b 'over',         'Execute line'
+        b 'into',         'Trace into expression'
+        b 'back',         'Go back one line'
+        b 'skip',         'Skip current line'
+        b 'cont-trace',   'Continue trace'
+        b 'cont-exec',    'Continue execution'
+        b 'restart',      'Restart all threads'
+        b 'edit-name',    'Edit name'
+        b 'quit',         'Quit this function'
+        b 'interrupt',    'Interrupt'
+        b 'cutback',      'Clear trace/stop/monitor for this object'
+        b 'line-numbers', 'Toggle line numbers'
+        b 'over',         'Execute line'
+        b 'into',         'Trace into expression'
+        ''
+        '<input class="search text-field">'
+        b 'next',         'Search for next match'
+        b 'prev',         'Search for previous match'
+        b 'case',         'Match case'
+      ].join ''}

-      [⋯]
-      ⍝
-      /⍝
-      ×
-
-      <input class="search text-field">
-      ▶
-      ◀
-      aA
-
-      +m
-      +f
-      +p
+      #{[
+        b 'line-numbers pressed', 'Toggle line numbers'
+        b 'comment', 'Comment selected text'
+        b 'uncomment', 'Uncomment selected text'
+        b 'save', 'Save changes and return'
+        ''
+        '<input class="search text-field">'
+        b 'next', 'Search for next match'
+        b 'prev', 'Search for previous match'
+        b 'case', 'Match case'
+        ''
+        b 'refac-m', 'Refactor text as method'
+        b 'refac-f', 'Refactor text as field'
+        b 'refac-p', 'Refactor text as property'
+      ].join ''}

-  '''
+  """
+  b = null

   k = # extra keys for CodeMirror
     Tab: -> c = cm.getCursor(); opts.autocomplete? cm.getLine(c.line), c.ch


Fixed a bug with playerNameClicked

Fixed a bug with playerNameClicked

--- client/client.coffee
+++ client/client.coffee
@@ -11,7 +11,6 @@
 Template.leaderboard.tournaments = Tournaments.find {}, {sort: {date: 1}}
 Template.leaderboard.scores = Scores.find {}, {sort: {scoreDate: 1}}

-this.playerNameClicked = false
 oldPlayerName = ""

 closeNewTournamentDialog = ->
@@ -75,12 +74,12 @@
 		removePlayerInputField(this, event, oldPlayerName)
 		oldPlayerName = ""
 	'click .player-name': (event) ->
-		if this.playerNameClicked and this.playerNameClicked is true
+		if Session.get("playerNameClicked") and Session.get("playerNameClicked") is true
 			return false
 		oldPlayerName = event.target.innerText
-		event.currentTarget.innerHTML = '<input type="text" class="change-player-name" autofocus="autofocus" value="' + event.currentTarget.innerText + '"/>
+		event.target.innerHTML = '<input type="text" class="change-player-name" autofocus="autofocus" value="' + event.target.innerText + '"/>
 		<button type="button" class="btn btn-danger remove-player-button" id="remove-'+event.target.id+'">Ta bort</button>'
-		this.playerNameClicked = true
+		Session.set("playerNameClicked", true)
 	'click #new-tournament': ->
 		if not newTournamentDialogOpen
 			newTournament = document.getElementById("new-tournament")


added placeholder text in search field

added placeholder text in search field

--- client/editor.coffee
+++ client/editor.coffee
@@ -25,7 +25,7 @@
         b 'tb-CBP',           'Clear trace/stop/monitor for this object'
         b 'tb-LN  last',      'Toggle line numbers'
         ''
-        '<input class="tb-search text-field">'
+        '<input class="tb-search text-field" placeholder="Search">'
         b 'tb-NX first',      'Search for next match'
         b 'tb-PV',            'Search for previous match'
         b 'tb-case last',     'Match case'
@@ -38,7 +38,7 @@
         b 'tb-AO',            'Comment selected text'
         b 'tb-DO  last',      'Uncomment selected text'
         ''
-        '<input class="tb-search text-field">'
+        '<input class="tb-search text-field" placeholder="Search">'
         b 'tb-NX  first',     'Search for next match'
         b 'tb-PV',            'Search for previous match'
         b 'tb-case last',     'Match case'


Rendering only calls it once

Rendering only calls it once

--- client/events.coffee
+++ client/events.coffee
@@ -77,7 +77,9 @@
     Meteor.Router.to "/search/#{encodeURIComponent(q)}"

 Template.topbar.rendered = ->
-  $(document).foundation()
+  if !window.foundation?
+    $(document).foundation -> window.foundation = true
+
   if !window._gaq?
     window._gaq = []
     _gaq.push(['_setAccount', 'UA-32638163-1'])


avoided misalignment of line numbers on ")ed f g"


only find backref when we expect one

only find backref when we expect one

--- client/images.coffee
+++ client/images.coffee
@@ -140,7 +140,7 @@
     BinaryData.update _id, updater_doc

     # get information from referencing collection
-    if 1
+    if coll_name?
       coll = window.get_collection_from_name(coll_name) # e.g. DriverLines
       orig = coll.findOne(_id: my_id) # get the document to which this image is being added
       myarr = []


simplified the way squiggleDescriptions is defined

--- client/keymap.coffee
+++ client/keymap.coffee
@@ -9,24 +9,23 @@
   if x != old then m = CodeMirror.keyMap.dyalog; m["'#{x}'"] = m["'#{old}'"]; delete m["'#{old}'"]
   return

-squiggleDescriptions = ((s) -> dict s.split(/\n| *│ */).map (l) -> [l[0], l[2..]]) '''
-  ¨ each              │ ← assignment         │ ⊤ encode (123→1 2 3) │ ⌹ matrix inv/div
-  ¯ negative          │ → branch             │ | abs/modulo         │ ⍷ find
-  ∨ or (GCD)          │ ⍺ left argument      │ ⍝ comment            │ ⍨ commute
-  ∧ and (LCM)         │ ⌈ ceil/max           │ ⍀ \\[⎕io]            │ ⍣ power operator
-  × signum/times      │ ⌊ floor/min          │ ⌿ /[⎕io]             │ ⍞ char I/O
-  ÷ reciprocal/divide │ ∇ recur              │ ⋄ statement sep      │ ⍬ zilde (⍳0)
-  ? roll/deal         │ ∘ compose            │ ⌶ I-beam             │ ⍤ rank
-  ⍵ right argument    │ ⎕ evaluated input    │ ⍒ grade down         │ ⌸ key
-  ∊ enlist/membership │ ⍎ execute            │ ⍋ grade up           │ ⌷ default/index
-  ⍴ shape/reshape     │ ⍕ format             │ ⌽ reverse/rotate     │ ≡ depth/match
-  ~ not/without       │ ⊢ right              │ ⍉ transpose          │ ≢ tally/not match
-  ↑ mix/take          │ ⊂ enclose/partition  │ ⊖ ⌽[⎕io]             │ ⊣ left
-  ↓ split/drop        │ ⊃ disclose/pick      │ ⍟ logarithm          │ ⍪ table / ,[⎕io]
-  ⍳ indices/index of  │ ∩ intersection       │ ⍱ nor                │ ⍠ variant
-  ○ pi/trig           │ ∪ unique/union       │ ⍲ nand
-  * exp/power         │ ⊥ decode (1 2 3→123) │ ! factorial/binomial
-'''
+squiggleDescriptions =
+  '¨': 'each',               '←': 'assignment',          '⊤': 'encode (123→1 2 3)',  '⌹': 'matrix inv/div'
+  '¯': 'negative',           '→': 'branch',              '|': 'abs/modulo',          '⍷': 'find'
+  '∨': 'or (GCD)',           '⍺': 'left argument',       '⍝': 'comment',             '⍨': 'commute'
+  '∧': 'and (LCM)',          '⌈': 'ceil/max',            '⍀': '\\[⎕io]',             '⍣': 'power operator'
+  '×': 'signum/times',       '⌊': 'floor/min',           '⌿': '/[⎕io]',              '⍞': 'char I/O'
+  '÷': 'reciprocal/divide',  '∇': 'recur',               '⋄': 'statement sep',       '⍬': 'zilde (⍳0)'
+  '?': 'roll/deal',          '∘': 'compose',             '⌶': 'I-beam',              '⍤': 'rank'
+  '⍵': 'right argument',     '⎕': 'evaluated input',     '⍒': 'grade down',          '⌸': 'key'
+  '∊': 'enlist/membership',  '⍎': 'execute',             '⍋': 'grade up',            '⌷': 'default/index'
+  '⍴': 'shape/reshape',      '⍕': 'format',              '⌽': 'reverse/rotate',      '≡': 'depth/match'
+  '~': 'not/without',        '⊢': 'right',               '⍉': 'transpose',           '≢': 'tally/not match'
+  '↑': 'mix/take',           '⊂': 'enclose/partition',   '⊖': '⌽[⎕io]',              '⊣': 'left'
+  '↓': 'split/drop',         '⊃': 'disclose/pick',       '⍟': 'logarithm',           '⍪': 'table / ,[⎕io]'
+  '⍳': 'indices/index of',   '∩': 'intersection',        '⍱': 'nor',                 '⍠': 'variant'
+  '○': 'pi/trig',            '∪': 'unique/union',        '⍲': 'nand'
+  '*': 'exp/power',          '⊥': 'decode (1 2 3→123)',  '!': 'factorial/binomial'

 ctid = 0 # backquote completion timeout id
 @forward = forward = {} # maps `x keys to squiggles


test second level

test second level

--- src/layout/test_layout.coffee
+++ src/layout/test_layout.coffee
@@ -40,7 +40,7 @@
     t.ok all_sprites = grid.find_child('sprites'), 'sprites'
     t.ok sprite = all_sprites.find_child(), 'sprite'
     console.log 'sprite', sprite, my.inspect(sprite)
-    t.equal sprite.get('position').all().toString(), "[1,1]", 'sprite position'
+    t.equal sprite.get('position').all().toString(), "2,2", 'sprite position'
     t.ok sprite.get('x') > 0, 'non-zero x'
     t.end()


current page between [1,offset] case fixed

--- src/pagination/coffee/services/pagination.coffee
+++ src/pagination/coffee/services/pagination.coffee
@@ -81,7 +81,7 @@
         if currentPage < paginationOffset + 1
           paginationOffset = paginationOffset + (paginationOffset - currentPage)
         else if totalPages - currentPage < paginationOffset + 1
-          paginationOffset = paginationOffset + (paginationOffset - (totalPages - currentPage))
+          paginationOffset = paginationOffset + (paginationOffset - (totalPages - currentPage)) + 1

         if currentPage - paginationOffset > 0
           start  = currentPage - paginationOffset


fix style problems part 2

fix style problems part 2

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -178,9 +178,9 @@

       module.prototype.load = (filename) ->
         @filename = filename
-        @paths = module._nodeModulePaths(path.dirname(filename))
-
-        extension = findExtension(filename)
+        @paths = module._nodeModulePaths path.dirname filename
+
+        extension = findExtension filename
         module._extensions[extension](this, filename)
         @loaded = true


[EVENTRIC-37] Fix specs

[EVENTRIC-37] Fix specs

--- src/mongodb_store.spec.coffee
+++ src/mongodb_store.spec.coffee
@@ -12,7 +12,8 @@
   mongoDbStore = null

   before ->
-    mongoDbStore = require './mongodb_store'
+    MongoDbStore = require './mongodb_store'
+    mongoDbStore = new MongoDbStore

   afterEach ->


Added support to open panes (now top, left, bottom, right) with proper OS command key combination displayed in the menu.

--- keymaps/shell.cson
+++ keymaps/shell.cson
@@ -7,5 +7,13 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/advanced/keymaps
-'.workspace':
-  'ctrl-alt-.': 'term:open-split-right'
+".platform-darwin":
+    "ctrl-alt-.": "term:open-split-right"
+    "ctrl-shift-alt-.": "term:open-split-left"
+    "ctrl-alt-,": "term:open-split-down"
+    "shift-ctrl-alt-,": "term:open-split-up"
+".platform-win32, .platform-linux":
+    "ctrl-alt-.": "term:open-split-right"
+    "shift-ctrl-alt-.": "term:open-split-left"
+    "ctrl-alt-,": "term:open-split-down"
+    "shift-ctrl-alt-,": "term:open-split-up"


Attempt to fix async audit tests

Attempt to fix async audit tests

--- test/integration/auditingIntegrationTests.coffee
+++ test/integration/auditingIntegrationTests.coffee
@@ -41,46 +41,51 @@
         setTimeout checkAudits, 100 * global.testTimeoutFactor

   describe "TLS Audit Server", ->
-
+
     it "should send TLS audit messages and save (valid)", (done) ->
-
+
       options =
         cert: fs.readFileSync "test/resources/trust-tls/cert1.pem"
         key:  fs.readFileSync "test/resources/trust-tls/key1.pem"
         ca: [ fs.readFileSync "test/resources/server-tls/cert.pem" ]
-
+
       client = tls.connect 5051, 'localhost', options, ->
         messagePrependlength = "#{testAuditMessage.length} #{testAuditMessage}"
         client.write messagePrependlength, (err) ->
           return done err if err
           client.end()
-
-      client.on 'end', -> Audit.find {}, (err, audits) ->
-        return done err if err
-
-        # message fields already validate heavily in unit test, just perform basic check
-        audits.length.should.be.exactly 2 # 1 extra due to automatic actor start audit
-        audits[1].rawMessage.should.be.exactly testAuditMessage
-        done()
-
+
+      client.on 'end', ->
+        checkAudits = -> Audit.find {}, (err, audits) ->
+          return done err if err
+
+          # message fields already validate heavily in unit test, just perform basic check
+          audits.length.should.be.exactly 2 # 1 extra due to automatic actor start audit
+          audits[1].rawMessage.should.be.exactly testAuditMessage
+          done()
+        setTimeout checkAudits, 100 * global.testTimeoutFactor
+
     it "should send TLS audit messages and NOT save (Invalid)", (done) ->
-
+
       options =
         cert: fs.readFileSync "test/resources/trust-tls/cert1.pem"
         key:  fs.readFileSync "test/resources/trust-tls/key1.pem"
         ca: [ fs.readFileSync "test/resources/server-tls/cert.pem" ]
-
+
       client = tls.connect 5051, 'localhost', options, ->
-        client.write testAuditMessage
-        client.end()
-
-      client.on 'end', -> Audit.find {}, (err, audits) ->
-        return done err if err
-
-        # message fields already validate heavily in unit test, just perform basic check
-        audits.length.should.be.exactly 1 # 1 extra due to automatic actor start audit
-        done()
-
+        client.write testAuditMessage, (err) ->
+          return done err if err
+          client.end()
+
+      client.on 'end', ->
+        checkAudits = -> Audit.find {}, (err, audits) ->
+          return done err if err
+
+          # message fields already validate heavily in unit test, just perform basic check
+          audits.length.should.be.exactly 1 # 1 extra due to automatic actor start audit
+          done()
+        setTimeout checkAudits, 100 * global.testTimeoutFactor
+
   describe "TCP Audit Server", ->
     it "should send TCP audit messages and save (valid)", (done) ->
       client = net.connect 5052, 'localhost', ->
@@ -88,25 +93,29 @@
         client.write messagePrependlength, (err) ->
           return done err if err
           client.end()
-
-      client.on 'end', -> Audit.find {}, (err, audits) ->
-        return done err if err
-
-        # message fields already validate heavily in unit test, just perform basic check
-        audits.length.should.be.exactly 2  # 1 extra due to automatic actor start audit
-        audits[1].rawMessage.should.be.exactly testAuditMessage
-        done()
-
+
+      client.on 'end', ->
+        checkAudits = -> Audit.find {}, (err, audits) ->
+          return done err if err
+
+          # message fields already validate heavily in unit test, just perform basic check
+          audits.length.should.be.exactly 2  # 1 extra due to automatic actor start audit
+          audits[1].rawMessage.should.be.exactly testAuditMessage
+          done()
+        setTimeout checkAudits, 100 * global.testTimeoutFactor
+
     it "should send TCP audit message and NOT save (Invalid)", (done) ->
       client = net.connect 5052, 'localhost', ->
         # testAuditMessage does not have message length with space prepended
         client.write testAuditMessage, (err) ->
           return done err if err
           client.end()
-
-      client.on 'end', -> Audit.find {}, (err, audits) ->
-        return done err if err
-
-        # message fields already validate heavily in unit test, just perform basic check
-        audits.length.should.be.exactly 1 # 1 extra due to automatic actor start audit
-        done()
+
+      client.on 'end', ->
+        checkAudits = -> Audit.find {}, (err, audits) ->
+          return done err if err
+
+          # message fields already validate heavily in unit test, just perform basic check
+          audits.length.should.be.exactly 1 # 1 extra due to automatic actor start audit
+          done()
+        setTimeout checkAudits, 100 * global.testTimeoutFactor


skip decodeURIComponent for iOS device

skip decodeURIComponent for iOS device

--- www/js/model.coffee
+++ www/js/model.coffee
@@ -300,7 +300,10 @@
 								# local file not found, create and download, resolve local entry
 								localfs.create path
 							.then (entry) =>
-						 		localfs.download @$url(), decodeURIComponent(entry.toURL()), opts, opts.progress
+								target = entry.toURL()
+								if device.platform != 'iOS'
+									target = decodeURIComponent target
+								localfs.download @$url(), target, opts, opts.progress
 									.then ->
 										entry
 					.then (entry) =>


Fix permanent option evaluation

Fix permanent option evaluation

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -185,7 +185,7 @@
     for u in users
       unless userId.equals u.id
         newUsers.push u
-      else if not permanent? and u.permanent
+      else if not permanent and u.permanent
         newUsers.push u

     return newUsers


Don't serialize annotations specially

Don't serialize annotations specially

--- app/api/classification.coffee
+++ app/api/classification.coffee
@@ -1,12 +1,6 @@
 {Resource, Model} = require 'json-api-client'
 counterpart = require 'counterpart'
 tasks = require '../classifier/tasks'
-
-class Annotation extends Model
-  toJSON: ->
-    result = {}
-    result[@task] = @value
-    result

 module.exports = class extends Resource
   annotations: null
@@ -20,7 +14,7 @@
     super

   annotate: (taskType, taskKey) ->
-    annotation = new Annotation task: taskKey, tasks[taskType].getDefaultAnnotation?()
+    annotation = new Model task: taskKey, tasks[taskType].getDefaultAnnotation?()
     annotation.listen [@, 'emit']

     @update annotations: =>


fixed offsetPosition problem

fixed offsetPosition problem

--- app/coffeescripts/Util.coffee
+++ app/coffeescripts/Util.coffee
@@ -50,7 +50,7 @@
   getElement: (id, list) ->
     _.find(list, (elem) ->  elem.get('id') == id)

-  offsetPosition: (pos) ->
+  _offsetPosition: (pos) ->
     pos.get('point')[0].set(lng: pos.get('point')[0].get('lng') - .0002)

   # creates a copy of of item array. The items array is a list menu items for context menus.


Update beta_, launch_requested keys

Update beta_, launch_requested keys

--- app/pages/lab/visibility.cjsx
+++ app/pages/lab/visibility.cjsx
@@ -10,12 +10,12 @@
     error: null
     setting:
       private: false
-      beta: false
-      launch: false
+      beta_requested: false
+      launch_requested: false

   render: ->
     looksDisabled =
-      opacity: 0.6
+      opacity: 0.7
       pointerEvents: 'none'

@@ -56,13 +56,13 @@

-          <button type="button" className="standard-button" disabled={@props.project.private or not @props.project.live or @state.setting.beta or @props.project.beta} onClick={@set.bind this, 'beta', true}>Apply for beta</button>{' '}
+          <button type="button" className="standard-button" disabled={@props.project.private or not @props.project.live or @state.setting.beta_requested or @props.project.beta_requested} onClick={@set.bind this, 'beta_requested', true}>Apply for beta</button>{' '}
           {if @props.project.private
             Only public projects can apply for a beta.
           else if not @props.project.live
             Only live projects can apply for a beta.
-          else if @props.project.beta
-            Beta status has been applied for. <button type="button" disabled={@state.setting.beta} onClick={@set.bind this, 'beta', false}>Cancel application</button>}
+          else if @props.project.beta_requested
+            Beta status has been applied for. <button type="button" disabled={@state.setting.beta_requested} onClick={@set.bind this, 'beta_requested', false}>Cancel application</button>}

Pending approval, expose this project to users who have opted in to help test new projects.

@@ -70,7 +70,7 @@
         {if @props.project.beta_approved

             Beta status for this project has been approved. To end the beta test and make changes, switch back to development mode.
-            {unless @props.project.launch or @props.project.launch_approved
+            {unless @props.project.launch_requested or @props.project.launch_approved
               If you’re ready to launch this project, see the next section.}

}

@@ -78,14 +78,14 @@

-            <button type="button" className="standard-button" disabled={not @props.project.beta_approved or @state.setting.launch or @props.project.launch} onClick={@set.bind this, 'launch', true}>Apply for full launch</button>{' '}
+            <button type="button" className="standard-button" disabled={not @props.project.beta_approved or @state.setting.launch_requested or @props.project.launch_requested} onClick={@set.bind this, 'launch_requested', true}>Apply for full launch</button>{' '}

             {unless @props.project.beta_approved
               Only projects in beta can apply for a full launch.}
             {if @props.project.launch_approved
               This project is available to the whole Zooniverse!
-            else if @props.project.launch
-              Launch is awaiting Zooniverse approval. <button type="button" disabled={@state.setting.launch} onClick={@set.bind this, 'launch', false}>Cancel application</button>}
+            else if @props.project.launch_requested
+              Launch is awaiting Zooniverse approval. <button type="button" disabled={@state.setting.launch_requested} onClick={@set.bind this, 'launch_requested', false}>Cancel application</button>}

Pending approval, expose this project to the entire Zooniverse through the main projects listing.


removing export of RESERVED ... more progress.

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -6,25 +6,27 @@
 # If included on a webpage, it will automatically sniff out, compile, and
 # execute all scripts present in `text/coffeescript` tags.

-fs               = require 'fs'
-path             = require 'path'
-{Lexer,RESERVED} = require './lexer'
-{parser}         = require './parser'
-vm               = require 'vm'
+fs        = require 'fs'
+path      = require 'path'
+{Lexer}   = require './lexer'
+{parser}  = require './parser'
+vm        = require 'vm'

-stripBOM = (content) ->
-  if content.charCodeAt(0) is 0xFEFF then content.substring 1 else content
+# The file extensions that are considered to be CoffeeScript.
+extensions = ['.coffee', '.litcoffee']
+
+# Load and run a CoffeeScript file for Node, stripping any `BOM`s.
+loadFile = (module, filename) ->
+  raw = fs.readFileSync filename, 'utf8'
+  stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw
+  module._compile compile(stripped, {filename}), filename

 if require.extensions
-  require.extensions['.coffee'] = (module, filename) ->
-    content = compile stripBOM(fs.readFileSync filename, 'utf8'), {filename}
-    module._compile content, filename
+  for ext in extensions
+    require.extensions[ext] = loadFile

 # The current CoffeeScript version number.
 exports.VERSION = '1.3.3'
-
-# Words that cannot be used as identifiers in CoffeeScript code
-exports.RESERVED = RESERVED

 # Expose helpers for testing.
 exports.helpers = require './helpers'
@@ -71,7 +73,7 @@
   mainModule.paths = require('module')._nodeModulePaths path.dirname fs.realpathSync options.filename

   # Compile.
-  if path.extname(mainModule.filename) isnt '.coffee' or require.extensions
+  if (path.extname(mainModule.filename) not in extensions) or require.extensions
     mainModule._compile compile(code, options), mainModule.filename
   else
     mainModule._compile code, mainModule.filename


Support for older revisions of Node.js for CoffeeScript.run()

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -54,7 +54,10 @@
   # Clear the module cache
   root.moduleCache = {} if root.moduleCache
   # Compile
-  root._compile exports.compile(code, options), root.filename
+  if path.extname(root.filename) isnt '.coffee' or require.extensions
+    root._compile exports.compile(code, options), root.filename
+  else
+    root._compile code, root.filename

 # Compile and evaluate a string of CoffeeScript (in a Node.js-like environment).
 # The CoffeeScript REPL uses this to run the input.


CoffeeScript 0.9.3

CoffeeScript 0.9.3

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -22,7 +22,7 @@
   helpers      = this.helpers

 # The current CoffeeScript version number.
-exports.VERSION = '0.9.2'
+exports.VERSION = '0.9.3'

 # Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
 # compiler.


some helpful console log

some helpful console log

--- coffee_server.coffee
+++ coffee_server.coffee
@@ -28,6 +28,7 @@
   false

 if detectIsAzure()
+  console.log('detected azure!!!')
   app.set('trust proxy')

 app.use( cors() )


Removed some logging.

Removed some logging.

--- app/views/params/range.coffee
+++ app/views/params/range.coffee
@@ -17,7 +17,6 @@
     @span = @$el.find('span')
     @value = @$el.find('.value')
     if @firstRender and typeof @model.get('value') isnt 'undefined'
-      console.log 'first render'
       @value.html @model.get('value').toFixed(2)
       position = (167 * (@model.get('value') - @min)) / (@max - @min)
       @span.css
@@ -28,7 +27,6 @@
     @

   onUserStartDrag: (e) =>
-    console.log 'starting drag'
     $('body').addClass 'unselectable'
     @dragging = true


Added the camera_{on|off} commands to the API controller

--- config/routes.coffee
+++ config/routes.coffee
@@ -1,12 +1,14 @@
 module.exports = ->
   @resources 'offices', ->
     @resources 'commands'
-
+
   @namespace 'api', ->
     @resources 'offices', 'api_offices', ->
       @namespace 'commands', ->
         @route 'microphone_on', 'api_commands#microphone_on'
         @route 'microphone_off', 'api_commands#microphone_off'
+        @route 'camera_on', 'api_commands#camera_on'
+        @route 'camera_off', 'api_commands#camera_off'
         @route 'alert', 'api_commands#alert'
-
+
       @resources 'commands', 'api_commands'


Update config/server to respond to all hello ids

Also respond with the an id so the url doesn't end up /#/hello/null.

--- config/server.coffee
+++ config/server.coffee
@@ -12,11 +12,11 @@
     defining a spec based on the needs of the client code that emerge.
  ###

-
 module.exports =
   drawRoutes: (app) ->
-    app.get '/hellos/1', (req, res) ->
+    app.get '/hellos/:hello_id', (req, res) ->
       res.json
         hello:
+          id: req.params.hello_id
           greeting: "Hello"
           audience: "World"


:new: Support language-hack in AutoComplete


project metrics: copy/paste mistake

project metrics: copy/paste mistake

--- src/smc-project/kucalc.coffee
+++ src/smc-project/kucalc.coffee
@@ -202,7 +202,7 @@
     kucalc_project_start_time{#{labels}} #{start_ts}
     # HELP kucalc_project_cpu_usage_seconds
     # TYPE kucalc_project_cpu_usage_seconds counter
-    kucalc_project_start_time{#{labels}} #{current_status.cpu?.usage ? 0.0}
+    kucalc_project_cpu_usage_seconds{#{labels}} #{current_status.cpu?.usage ? 0.0}
     # HELP kucalc_project_disk_usage_mb
     # TYPE kucalc_project_disk_usage_mb gauge
     kucalc_project_disk_usage_mb{#{labels}} #{current_status.disk_MB ? 0.0}


Check type of the loc and type check

Check type of the loc and type check

--- routes/api.coffee
+++ routes/api.coffee
@@ -46,10 +46,11 @@
         street: hospital_data.street
         city: hospital_data.city
         zip: hospital_data.zip
+        loc: hospital_data.loc
         state: hospital_data.state
         rating: hospital_data.rating

-      output.push(temp)
+      output.push(temp) if temp.loc

     res.json output


last snapshot: i got the sort order wrong (only obvious with multiple snapshot servers).

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -1827,7 +1827,7 @@
                     if err
                         cb(err)
                     else
-                        results.sort((a,b) -> a.utc_seconds_epoch - b.utc_seconds_epoch)
+                        results.sort((a,b) -> b.utc_seconds_epoch - a.utc_seconds_epoch)
                         opts.cb(false, results)
     ], opts.cb)


added possibility for permissions or permissions[key] beeing a check function

--- lib/Context.coffee
+++ lib/Context.coffee
@@ -158,7 +158,7 @@
             floyd.tools.objects.intercept @, 'destroy', (done, destroy)=>

                 if @stop && @_status.indexOf('stopped') is -1
-                    return @logger.warn 'context not stopped!'
+                    return @logger.warning 'context not stopped!'

                 @_init 'destroy', null, (err)=>
                     done?(err) if err
@@ -338,7 +338,7 @@
                 ,

                     (next)=> # custom check function - must callback true to permit!
-                        if check = (@data.permissions?[key]?.check || @data.permissions?.check)
+                        if typeof (check = @data.permissions) is 'function' || typeof (check = @data.permissions?[key]) is 'function' || check = (@data.permissions?[key]?.check || @data.permissions?.check)

                             check identity, key, args, next


improve validation

improve validation

--- lib/atomizr.coffee
+++ lib/atomizr.coffee
@@ -122,16 +122,20 @@
       atom.notifications.addError("Atomizr", detail: e, dismissable: true)
       return

-    try
-      output = JSON.stringify(data[Object.keys(data)[0]], null, 2)
-    catch e
-      atom.notifications.addError("Atomizr", detail: e, dismissable: true)
-      return
-
-    # Write back to editor and change scope
-    editor.setText(output)
-    editor.setGrammar(atom.grammars.grammarForScopeName('source.json.'))
-    @renameFile(editor, "json")
+    if Object.keys(data)[0][0] is "."
+      try
+        output = JSON.stringify(data[Object.keys(data)[0]], null, 2)
+      catch e
+        atom.notifications.addError("Atomizr", detail: e, dismissable: true)
+        return
+
+      # Write back to editor and change scope
+      editor.setText(output)
+      editor.setGrammar(atom.grammars.grammarForScopeName('source.json.'))
+      @renameFile(editor, "json")
+    else
+      atom.notifications.addWarning("Atomizr", detail: "This doesn't seem to be a valid Atom file. Aborting.", dismissable: false)
+      return

   # Convert Sublime Text completions into Atom snippet
   sublToAtom: ->
@@ -254,6 +258,10 @@
       data = parseJson(input)
     catch e
       atom.notifications.addError("Atomizr", detail: e, dismissable: true)
+      return
+
+    if Object.keys(data)[0][0] is "." or data.completions? is true
+      atom.notifications.addWarning("Atomizr", detail: "This doesn't seem to be a valid Visual Studio Code file. Aborting.", dismissable: false)
       return

     output =