浏览代码

Merge branch 'main' into mdx

Henning Dieterichs 2 年之前
父节点
当前提交
19da7ede28

+ 24 - 0
src/basic-languages/cypher/cypher.contribution.ts

@@ -0,0 +1,24 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { registerLanguage } from '../_.contribution';
+
+declare var AMD: any;
+declare var require: any;
+
+registerLanguage({
+	id: 'cypher',
+	extensions: ['.cypher', '.cyp'],
+	aliases: ['Cypher', 'OpenCypher'],
+	loader: () => {
+		if (AMD) {
+			return new Promise((resolve, reject) => {
+				require(['vs/basic-languages/cypher/cypher'], resolve, reject);
+			});
+		} else {
+			return import('./cypher');
+		}
+	}
+});

+ 327 - 0
src/basic-languages/cypher/cypher.test.ts

@@ -0,0 +1,327 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { testTokenization } from '../test/testRunner';
+
+testTokenization('cypher', [
+	// Comments
+	[
+		{
+			line: '// Single line comment',
+			tokens: [{ startIndex: 0, type: 'comment.cypher' }]
+		}
+	],
+	[
+		{
+			line: 'MATCH /* comment part */ xyz',
+			tokens: [
+				{ startIndex: 0, type: 'keyword.cypher' },
+				{ startIndex: 5, type: 'white.cypher' },
+				{ startIndex: 6, type: 'comment.cypher' },
+				{ startIndex: 24, type: 'white.cypher' },
+				{ startIndex: 25, type: 'identifier.cypher' }
+			]
+		}
+	],
+	[
+		{
+			line: '/* multi line comment',
+			tokens: [{ startIndex: 0, type: 'comment.cypher' }]
+		},
+		{
+			line: 'comment continues MATCH // not done yet',
+			tokens: [{ startIndex: 0, type: 'comment.cypher' }]
+		},
+		{
+			line: 'comment ends */ MATCH',
+			tokens: [
+				{ startIndex: 0, type: 'comment.cypher' },
+				{ startIndex: 15, type: 'white.cypher' },
+				{ startIndex: 16, type: 'keyword.cypher' }
+			]
+		}
+	],
+
+	// Numbers: A decimal (integer or float) literal:
+	[
+		{
+			line: '13',
+			tokens: [{ startIndex: 0, type: 'number.cypher' }]
+		}
+	],
+	[
+		{
+			line: '-40000',
+			tokens: [{ startIndex: 0, type: 'number.cypher' }]
+		}
+	],
+	[
+		{
+			line: '3.14',
+			tokens: [{ startIndex: 0, type: 'number.float.cypher' }]
+		}
+	],
+	[
+		{
+			line: '.314',
+			tokens: [{ startIndex: 0, type: 'number.float.cypher' }]
+		}
+	],
+	[
+		{
+			line: '-.314',
+			tokens: [{ startIndex: 0, type: 'number.float.cypher' }]
+		}
+	],
+	[
+		{
+			line: '6.022E23',
+			tokens: [{ startIndex: 0, type: 'number.float.cypher' }]
+		}
+	],
+	[
+		{
+			line: '-6.022e23',
+			tokens: [{ startIndex: 0, type: 'number.float.cypher' }]
+		}
+	],
+	[
+		{
+			line: '12E10',
+			tokens: [{ startIndex: 0, type: 'number.float.cypher' }]
+		}
+	],
+	[
+		{
+			line: '12e10',
+			tokens: [{ startIndex: 0, type: 'number.float.cypher' }]
+		}
+	],
+	[
+		{
+			line: '12e-10',
+			tokens: [{ startIndex: 0, type: 'number.float.cypher' }]
+		}
+	],
+	[
+		{
+			line: '12E-10',
+			tokens: [{ startIndex: 0, type: 'number.float.cypher' }]
+		}
+	],
+
+	// Numbers: A hexadecimal integer literal (starting with 0x)
+	[
+		{
+			line: '0x13af',
+			tokens: [{ startIndex: 0, type: 'number.hex.cypher' }]
+		}
+	],
+	[
+		{
+			line: '0xFC3A9',
+			tokens: [{ startIndex: 0, type: 'number.hex.cypher' }]
+		}
+	],
+	[
+		{
+			line: '-0x66eff',
+			tokens: [{ startIndex: 0, type: 'number.hex.cypher' }]
+		}
+	],
+
+	// Numbers: An octal integer literal (starting with 0)
+	[
+		{
+			line: '01372',
+			tokens: [{ startIndex: 0, type: 'number.octal.cypher' }]
+		}
+	],
+	[
+		{
+			line: '02127',
+			tokens: [{ startIndex: 0, type: 'number.octal.cypher' }]
+		}
+	],
+	[
+		{
+			line: '-05671',
+			tokens: [{ startIndex: 0, type: 'number.octal.cypher' }]
+		}
+	],
+
+	// Strings: A String literal ('', ""), escaped and non-escaped
+	[
+		{
+			line: '"two \'words\'"',
+			tokens: [{ startIndex: 0, type: 'string.cypher' }]
+		}
+	],
+	[
+		{
+			line: '"two \\"words\\""',
+			tokens: [{ startIndex: 0, type: 'string.cypher' }]
+		}
+	],
+	[
+		{
+			line: '\'two "words"\'',
+			tokens: [{ startIndex: 0, type: 'string.cypher' }]
+		}
+	],
+	[
+		{
+			line: "'two \\'words\\''",
+			tokens: [{ startIndex: 0, type: 'string.cypher' }]
+		}
+	],
+
+	// Identifiers wrapped with backtick (``)
+	[
+		{
+			line: '`variable`',
+			tokens: [{ startIndex: 0, type: 'identifier.escape.cypher' }]
+		}
+	],
+	[
+		{
+			line: '`A variable with weird stuff in it[]!`',
+			tokens: [{ startIndex: 0, type: 'identifier.escape.cypher' }]
+		}
+	],
+	[
+		{
+			line: '`Escaped \\`variable\\``',
+			tokens: [{ startIndex: 0, type: 'identifier.escape.cypher' }]
+		}
+	],
+
+	// Operators
+	[
+		{
+			line: '1+2',
+			tokens: [
+				{ startIndex: 0, type: 'number.cypher' },
+				{ startIndex: 1, type: 'delimiter.cypher' },
+				{ startIndex: 2, type: 'number.cypher' }
+			]
+		}
+	],
+	[
+		{
+			line: '1++2',
+			tokens: [
+				{ startIndex: 0, type: 'number.cypher' },
+				{ startIndex: 1, type: '' },
+				{ startIndex: 3, type: 'number.cypher' }
+			]
+		}
+	],
+
+	// Builtin literals: A boolean literal (true | false)
+	[
+		{
+			line: 'true',
+			tokens: [{ startIndex: 0, type: 'predefined.literal.cypher' }]
+		}
+	],
+	[
+		{
+			line: 'false',
+			tokens: [{ startIndex: 0, type: 'predefined.literal.cypher' }]
+		}
+	],
+	[
+		{
+			line: 'TRUE',
+			tokens: [{ startIndex: 0, type: 'predefined.literal.cypher' }]
+		}
+	],
+	[
+		{
+			line: 'FALSE',
+			tokens: [{ startIndex: 0, type: 'predefined.literal.cypher' }]
+		}
+	],
+
+	// Builtin literals: A null literal
+	[
+		{
+			line: 'null',
+			tokens: [{ startIndex: 0, type: 'predefined.literal.cypher' }]
+		}
+	],
+	[
+		{
+			line: 'NULL',
+			tokens: [{ startIndex: 0, type: 'predefined.literal.cypher' }]
+		}
+	],
+
+	// Builtin functions
+	[
+		{
+			line: 'properties(node)',
+			tokens: [
+				{ startIndex: 0, type: 'predefined.function.cypher' },
+				{ startIndex: 10, type: 'delimiter.parenthesis.cypher' },
+				{ startIndex: 11, type: 'identifier.cypher' },
+				{ startIndex: 15, type: 'delimiter.parenthesis.cypher' }
+			]
+		}
+	],
+	[
+		{
+			line: 'left(right("Hello Cypher"))',
+			tokens: [
+				{ startIndex: 0, type: 'predefined.function.cypher' },
+				{ startIndex: 4, type: 'delimiter.parenthesis.cypher' },
+				{ startIndex: 5, type: 'predefined.function.cypher' },
+				{ startIndex: 10, type: 'delimiter.parenthesis.cypher' },
+				{ startIndex: 11, type: 'string.cypher' },
+				{ startIndex: 25, type: 'delimiter.parenthesis.cypher' }
+			]
+		}
+	],
+
+	// Keywords
+	[
+		{
+			line: 'MATCH (n) RETURN n',
+			tokens: [
+				{ startIndex: 0, type: 'keyword.cypher' },
+				{ startIndex: 5, type: 'white.cypher' },
+				{ startIndex: 6, type: 'delimiter.parenthesis.cypher' },
+				{ startIndex: 7, type: 'identifier.cypher' },
+				{ startIndex: 8, type: 'delimiter.parenthesis.cypher' },
+				{ startIndex: 9, type: 'white.cypher' },
+				{ startIndex: 10, type: 'keyword.cypher' },
+				{ startIndex: 16, type: 'white.cypher' },
+				{ startIndex: 17, type: 'identifier.cypher' }
+			]
+		}
+	],
+
+	// Labels on nodes and relationships
+	[
+		{
+			line: '(n:NodeLabel1)-[:RelationshipType]->(:NodeLabel2:NodeLabel3)',
+			tokens: [
+				{ startIndex: 0, type: 'delimiter.parenthesis.cypher' },
+				{ startIndex: 1, type: 'identifier.cypher' },
+				{ startIndex: 2, type: 'type.identifier.cypher' },
+				{ startIndex: 13, type: 'delimiter.parenthesis.cypher' },
+				{ startIndex: 14, type: 'delimiter.cypher' },
+				{ startIndex: 15, type: 'delimiter.bracket.cypher' },
+				{ startIndex: 16, type: 'type.identifier.cypher' },
+				{ startIndex: 33, type: 'delimiter.bracket.cypher' },
+				{ startIndex: 34, type: 'delimiter.cypher' },
+				{ startIndex: 36, type: 'delimiter.parenthesis.cypher' },
+				{ startIndex: 37, type: 'type.identifier.cypher' },
+				{ startIndex: 59, type: 'delimiter.parenthesis.cypher' }
+			]
+		}
+	]
+]);

+ 274 - 0
src/basic-languages/cypher/cypher.ts

@@ -0,0 +1,274 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { languages } from '../../fillers/monaco-editor-core';
+
+export const conf: languages.LanguageConfiguration = {
+	comments: {
+		lineComment: '//',
+		blockComment: ['/*', '*/']
+	},
+	brackets: [
+		['{', '}'],
+		['[', ']'],
+		['(', ')']
+	],
+	autoClosingPairs: [
+		{ open: '{', close: '}' },
+		{ open: '[', close: ']' },
+		{ open: '(', close: ')' },
+		{ open: '"', close: '"' },
+		{ open: "'", close: "'" },
+		{ open: '`', close: '`' }
+	],
+	surroundingPairs: [
+		{ open: '{', close: '}' },
+		{ open: '[', close: ']' },
+		{ open: '(', close: ')' },
+		{ open: '"', close: '"' },
+		{ open: "'", close: "'" },
+		{ open: '`', close: '`' }
+	]
+};
+
+// Ref: Cypher Query Language Reference, Version 9 (https://opencypher.org/resources/)
+export const language = <languages.IMonarchLanguage>{
+	defaultToken: '',
+	tokenPostfix: `.cypher`,
+	ignoreCase: true,
+
+	brackets: [
+		{ open: '{', close: '}', token: 'delimiter.curly' },
+		{ open: '[', close: ']', token: 'delimiter.bracket' },
+		{ open: '(', close: ')', token: 'delimiter.parenthesis' }
+	],
+
+	keywords: [
+		'ALL',
+		'AND',
+		'AS',
+		'ASC',
+		'ASCENDING',
+		'BY',
+		'CALL',
+		'CASE',
+		'CONTAINS',
+		'CREATE',
+		'DELETE',
+		'DESC',
+		'DESCENDING',
+		'DETACH',
+		'DISTINCT',
+		'ELSE',
+		'END',
+		'ENDS',
+		'EXISTS',
+		'IN',
+		'IS',
+		'LIMIT',
+		'MANDATORY',
+		'MATCH',
+		'MERGE',
+		'NOT',
+		'ON',
+		'ON',
+		'OPTIONAL',
+		'OR',
+		'ORDER',
+		'REMOVE',
+		'RETURN',
+		'SET',
+		'SKIP',
+		'STARTS',
+		'THEN',
+		'UNION',
+		'UNWIND',
+		'WHEN',
+		'WHERE',
+		'WITH',
+		'XOR',
+		'YIELD'
+	],
+	builtinLiterals: ['true', 'TRUE', 'false', 'FALSE', 'null', 'NULL'],
+	builtinFunctions: [
+		'abs',
+		'acos',
+		'asin',
+		'atan',
+		'atan2',
+		'avg',
+		'ceil',
+		'coalesce',
+		'collect',
+		'cos',
+		'cot',
+		'count',
+		'degrees',
+		'e',
+		'endNode',
+		'exists',
+		'exp',
+		'floor',
+		'head',
+		'id',
+		'keys',
+		'labels',
+		'last',
+		'left',
+		'length',
+		'log',
+		'log10',
+		'lTrim',
+		'max',
+		'min',
+		'nodes',
+		'percentileCont',
+		'percentileDisc',
+		'pi',
+		'properties',
+		'radians',
+		'rand',
+		'range',
+		'relationships',
+		'replace',
+		'reverse',
+		'right',
+		'round',
+		'rTrim',
+		'sign',
+		'sin',
+		'size',
+		'split',
+		'sqrt',
+		'startNode',
+		'stDev',
+		'stDevP',
+		'substring',
+		'sum',
+		'tail',
+		'tan',
+		'timestamp',
+		'toBoolean',
+		'toFloat',
+		'toInteger',
+		'toLower',
+		'toString',
+		'toUpper',
+		'trim',
+		'type'
+	],
+
+	operators: [
+		// Math operators
+		'+',
+		'-',
+		'*',
+		'/',
+		'%',
+		'^',
+		// Comparison operators
+		'=',
+		'<>',
+		'<',
+		'>',
+		'<=',
+		'>=',
+		// Pattern operators
+		'->',
+		'<-',
+		'-->',
+		'<--'
+	],
+
+	escapes: /\\(?:[tbnrf\\"'`]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
+	digits: /\d+/,
+	octaldigits: /[0-7]+/,
+	hexdigits: /[0-9a-fA-F]+/,
+
+	tokenizer: {
+		root: [[/[{}[\]()]/, '@brackets'], { include: 'common' }],
+		common: [
+			{ include: '@whitespace' },
+			{ include: '@numbers' },
+			{ include: '@strings' },
+
+			// Cypher labels on nodes/relationships, e.g. (n:NodeLabel)-[e:RelationshipLabel]
+			[/:[a-zA-Z_][\w]*/, 'type.identifier'],
+			[
+				/[a-zA-Z_][\w]*(?=\()/,
+				{
+					cases: {
+						'@builtinFunctions': 'predefined.function'
+					}
+				}
+			],
+			[
+				/[a-zA-Z_$][\w$]*/,
+				{
+					cases: {
+						'@keywords': 'keyword',
+						'@builtinLiterals': 'predefined.literal',
+						'@default': 'identifier'
+					}
+				}
+			],
+			[/`/, 'identifier.escape', '@identifierBacktick'],
+
+			// delimiter and operator after number because of `.\d` floats and `:` in labels
+			[/[;,.:|]/, 'delimiter'],
+			[
+				/[<>=%+\-*/^]+/,
+				{
+					cases: {
+						'@operators': 'delimiter',
+						'@default': ''
+					}
+				}
+			]
+		],
+		numbers: [
+			[/-?(@digits)[eE](-?(@digits))?/, 'number.float'],
+			[/-?(@digits)?\.(@digits)([eE]-?(@digits))?/, 'number.float'],
+			[/-?0x(@hexdigits)/, 'number.hex'],
+			[/-?0(@octaldigits)/, 'number.octal'],
+			[/-?(@digits)/, 'number']
+		],
+		strings: [
+			[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
+			[/'([^'\\]|\\.)*$/, 'string.invalid'], // non-teminated string
+			[/"/, 'string', '@stringDouble'],
+			[/'/, 'string', '@stringSingle']
+		],
+		whitespace: [
+			[/[ \t\r\n]+/, 'white'],
+			[/\/\*/, 'comment', '@comment'],
+			[/\/\/.*$/, 'comment']
+		],
+		comment: [
+			[/\/\/.*/, 'comment'],
+			[/[^/*]+/, 'comment'],
+			[/\*\//, 'comment', '@pop'],
+			[/[/*]/, 'comment']
+		],
+		stringDouble: [
+			[/[^\\"]+/, 'string'],
+			[/@escapes/, 'string'],
+			[/\\./, 'string.invalid'],
+			[/"/, 'string', '@pop']
+		],
+		stringSingle: [
+			[/[^\\']+/, 'string'],
+			[/@escapes/, 'string'],
+			[/\\./, 'string.invalid'],
+			[/'/, 'string', '@pop']
+		],
+		identifierBacktick: [
+			[/[^\\`]+/, 'identifier.escape'],
+			[/@escapes/, 'identifier.escape'],
+			[/\\./, 'identifier.escape.invalid'],
+			[/`/, 'identifier.escape', '@pop']
+		]
+	}
+};

+ 1 - 0
src/basic-languages/monaco.contribution.ts

@@ -15,6 +15,7 @@ import './cpp/cpp.contribution';
 import './csharp/csharp.contribution';
 import './csp/csp.contribution';
 import './css/css.contribution';
+import './cypher/cypher.contribution';
 import './dart/dart.contribution';
 import './dockerfile/dockerfile.contribution';
 import './ecl/ecl.contribution';

+ 0 - 35
src/basic-languages/mysql/mysql.test.ts

@@ -385,41 +385,6 @@ testTokenization('mysql', [
 		}
 	],
 
-	[
-		{
-			line: 'declare `abc 321`;',
-			tokens: [
-				{ startIndex: 0, type: 'keyword.sql' },
-				{ startIndex: 7, type: 'white.sql' },
-				{ startIndex: 8, type: 'identifier.quote.sql' },
-				{ startIndex: 9, type: 'identifier.sql' },
-				{ startIndex: 16, type: 'identifier.quote.sql' },
-				{ startIndex: 17, type: 'delimiter.sql' }
-			]
-		}
-	],
-
-	[
-		{
-			line: '`abc`` 321 `` xyz`',
-			tokens: [
-				{ startIndex: 0, type: 'identifier.quote.sql' },
-				{ startIndex: 1, type: 'identifier.sql' },
-				{ startIndex: 17, type: 'identifier.quote.sql' }
-			]
-		}
-	],
-
-	[
-		{
-			line: '`abc',
-			tokens: [
-				{ startIndex: 0, type: 'identifier.quote.sql' },
-				{ startIndex: 1, type: 'identifier.sql' }
-			]
-		}
-	],
-
 	[
 		{
 			line: 'int',

+ 4 - 0
webpack-plugin/README.md

@@ -78,8 +78,12 @@ Options can be passed in to `MonacoWebpackPlugin`. They can be used to generate
   | handlebars | html         |
   | scss, less | css          |
 
+  To view a list of all available languages, you can run `import metadata from 'monaco-editor/esm/metadata'; console.log(metadata.languages);`.
+
 - `features` (`string[]`) - include only a subset of the editor features. By default, all features shipped with the `monaco-editor` will be included. Instead of enumerating included features, it is also possible to exclude certain default features prefixing them with an exclamation mark '!'.
 
+  To view a list of all available features, you can run `import metadata from 'monaco-editor/esm/metadata'; console.log(metadata.features);`.
+
 - `globalAPI` (`boolean`) - specify whether the editor API should be exposed through a global `monaco` object or not. This option is applicable to `0.22.0` and newer version of `monaco-editor`. Since `0.22.0`, the ESM version of the monaco editor does no longer define a global `monaco` object unless `global.MonacoEnvironment = { globalAPI: true }` is set ([change log](https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md#0220-29012021)).
   - default value: `false`.
 

+ 3 - 0
website/index/samples/sample.cypher.txt

@@ -0,0 +1,3 @@
+MATCH (nicole:Actor {name: 'Nicole Kidman'})-[:ACTED_IN]->(movie:Movie)
+WHERE movie.year < $yearParameter
+RETURN movie

+ 18 - 10
website/index/samples/sample.dart.txt

@@ -1,21 +1,24 @@
-import 'dart:async';
 import 'dart:math' show Random;
-main() async {
+
+void main() async {
   print('Compute π using the Monte Carlo method.');
-  await for (var estimate in computePi().take(100)) {
+  await for (final estimate in computePi().take(100)) {
     print('π ≅ $estimate');
   }
 }
+
 /// Generates a stream of increasingly accurate estimates of π.
-Stream<double> computePi({int batch: 100000}) async* {
-  var total = 0;
+Stream<double> computePi({int batch = 100000}) async* {
+  var total = 0; // Inferred to be of type int
   var count = 0;
   while (true) {
-    var points = generateRandom().take(batch);
-    var inside = points.where((p) => p.isInsideUnitCircle);
+    final points = generateRandom().take(batch);
+    final inside = points.where((p) => p.isInsideUnitCircle);
+
     total += batch;
     count += inside.length;
-    var ratio = count / total;
+    final ratio = count / total;
+
     // Area of a circle is A = π⋅r², therefore π = A/r².
     // So, when given random points with x ∈ <0,1>,
     // y ∈ <0,1>, the ratio of those inside a unit circle
@@ -24,14 +27,19 @@ Stream<double> computePi({int batch: 100000}) async* {
     yield ratio * 4;
   }
 }
-Iterable<Point> generateRandom([int seed]) sync* {
+
+Iterable<Point> generateRandom([int? seed]) sync* {
   final random = Random(seed);
   while (true) {
     yield Point(random.nextDouble(), random.nextDouble());
   }
 }
+
 class Point {
-  final double x, y;
+  final double x;
+  final double y;
+
   const Point(this.x, this.y);
+
   bool get isInsideUnitCircle => x * x + y * y <= 1;
 }

+ 6 - 0
website/playground/new-samples/all.js

@@ -156,6 +156,12 @@
 			id: 'extending-language-services-hover-provider-example',
 			path: 'extending-language-services/hover-provider-example'
 		},
+		{
+			chapter: 'Extending Language Services',
+			name: 'Model markers example',
+			id: 'extending-language-services-model-markers-example',
+			path: 'extending-language-services/model-markers-example'
+		},
 		{
 			chapter: 'Extending Language Services',
 			name: 'Semantic tokens provider example',

+ 0 - 0
website/playground/new-samples/extending-language-services/model-markers-example/sample.css


+ 1 - 0
website/playground/new-samples/extending-language-services/model-markers-example/sample.html

@@ -0,0 +1 @@
+<div id="container" style="height: 100%"></div>

+ 48 - 0
website/playground/new-samples/extending-language-services/model-markers-example/sample.js

@@ -0,0 +1,48 @@
+function validate(model) {
+	const markers = [];
+	// lines start at 1
+	for (let i = 1; i < model.getLineCount() + 1; i++) {
+		const range = {
+			startLineNumber: i,
+			startColumn: 1,
+			endLineNumber: i,
+			endColumn: model.getLineLength(i) + 1
+		};
+		const content = model.getValueInRange(range).trim();
+		const number = Number(content);
+		if (Number.isNaN(number)) {
+			markers.push({
+				message: 'not a number',
+				severity: monaco.MarkerSeverity.Error,
+				startLineNumber: range.startLineNumber,
+				startColumn: range.startColumn,
+				endLineNumber: range.endLineNumber,
+				endColumn: range.endColumn
+			});
+		} else if (!Number.isInteger(number)) {
+			markers.push({
+				message: 'not an integer',
+				severity: monaco.MarkerSeverity.Warning,
+				startLineNumber: range.startLineNumber,
+				startColumn: range.startColumn,
+				endLineNumber: range.endLineNumber,
+				endColumn: range.endColumn
+			});
+		}
+	}
+	monaco.editor.setModelMarkers(model, 'owner', markers);
+}
+
+const value = `12345
+abcd
+234.56
+12345
+abcd
+234.56`;
+const uri = monaco.Uri.parse('inmemory://test');
+const model = monaco.editor.createModel(value, 'demoLanguage', uri);
+editor = monaco.editor.create(document.getElementById('container'), { model });
+validate(model);
+model.onDidChangeContent(() => {
+	validate(model);
+});