mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 09:36:27 +00:00
526 lines
14 KiB
TypeScript
526 lines
14 KiB
TypeScript
// Copied from https://github.com/microsoft/monaco-languages/blob/d7cc098c481059f63d51ce3753975c8ca8ab6030/src/python/python.ts
|
|
|
|
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import type * as monaco from 'monaco-editor';
|
|
|
|
/** The Pybricks MicroPython language identifier. */
|
|
export const pybricksMicroPythonId = 'pybricks-micropython';
|
|
|
|
export const conf: monaco.languages.LanguageConfiguration = {
|
|
comments: {
|
|
lineComment: '#',
|
|
blockComment: ["'''", "'''"],
|
|
},
|
|
brackets: [
|
|
['{', '}'],
|
|
['[', ']'],
|
|
['(', ')'],
|
|
],
|
|
autoClosingPairs: [
|
|
{ open: '{', close: '}' },
|
|
{ open: '[', close: ']' },
|
|
{ open: '(', close: ')' },
|
|
{ open: '"', close: '"', notIn: ['string'] },
|
|
{ open: "'", close: "'", notIn: ['string', 'comment'] },
|
|
],
|
|
surroundingPairs: [
|
|
{ open: '{', close: '}' },
|
|
{ open: '[', close: ']' },
|
|
{ open: '(', close: ')' },
|
|
{ open: '"', close: '"' },
|
|
{ open: "'", close: "'" },
|
|
],
|
|
onEnterRules: [
|
|
{
|
|
beforeText: new RegExp(
|
|
'^\\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async).*?:\\s*$',
|
|
),
|
|
action: { indentAction: <monaco.languages.IndentAction.Indent>1 },
|
|
},
|
|
],
|
|
folding: {
|
|
offSide: true,
|
|
markers: {
|
|
start: new RegExp('^\\s*#region\\b'),
|
|
end: new RegExp('^\\s*#endregion\\b'),
|
|
},
|
|
},
|
|
};
|
|
|
|
export const language = <monaco.languages.IMonarchLanguage>{
|
|
defaultToken: '',
|
|
tokenPostfix: '.python',
|
|
|
|
// https://docs.python.org/3/reference/lexical_analysis.html#keywords
|
|
keywords: <ReadonlyArray<string>>[
|
|
'False',
|
|
'None',
|
|
'True',
|
|
'and',
|
|
'as',
|
|
'assert',
|
|
'async',
|
|
'await',
|
|
'break',
|
|
'class',
|
|
'continue',
|
|
'def',
|
|
'del',
|
|
'elif',
|
|
'else',
|
|
'except',
|
|
'finally',
|
|
'for',
|
|
'from',
|
|
'global',
|
|
'if',
|
|
'import',
|
|
'in',
|
|
'is',
|
|
'lambda',
|
|
'nonlocal',
|
|
'not',
|
|
'or',
|
|
'pass',
|
|
'raise',
|
|
'return',
|
|
'try',
|
|
'while',
|
|
'with',
|
|
'yield',
|
|
],
|
|
|
|
// https://docs.python.org/3/library/functions.html#built-in-funcs
|
|
builtins: <ReadonlyArray<string>>[
|
|
'abs',
|
|
'all',
|
|
'any',
|
|
'ascii',
|
|
'bin',
|
|
'bool',
|
|
'breakpoint',
|
|
'bytearray',
|
|
'bytes',
|
|
'callable',
|
|
'chr',
|
|
'classmethod',
|
|
'compile',
|
|
'complex',
|
|
'delattr',
|
|
'dict',
|
|
'dir',
|
|
'divmod',
|
|
'enumerate',
|
|
'eval',
|
|
'exec',
|
|
'filter',
|
|
'float',
|
|
'format',
|
|
'frozenset',
|
|
'getattr',
|
|
'globals',
|
|
'hasattr',
|
|
'hash',
|
|
'help',
|
|
'hex',
|
|
'id',
|
|
'input',
|
|
'int',
|
|
'isinstance',
|
|
'issubclass',
|
|
'iter',
|
|
'len',
|
|
'list',
|
|
'locals',
|
|
'map',
|
|
'max',
|
|
'memoryview',
|
|
'min',
|
|
'next',
|
|
'object',
|
|
'oct',
|
|
'open',
|
|
'ord',
|
|
'pow',
|
|
'print',
|
|
'property',
|
|
'reversed',
|
|
'range',
|
|
'repr',
|
|
'reversed',
|
|
'round',
|
|
'self',
|
|
'set',
|
|
'setattr',
|
|
'slice',
|
|
'sorted',
|
|
'staticmethod',
|
|
'str',
|
|
'sum',
|
|
'super',
|
|
'tuple',
|
|
'type',
|
|
'vars',
|
|
'zip',
|
|
'__import__',
|
|
],
|
|
|
|
brackets: [
|
|
{ open: '{', close: '}', token: 'delimiter.curly' },
|
|
{ open: '[', close: ']', token: 'delimiter.bracket' },
|
|
{ open: '(', close: ')', token: 'delimiter.parenthesis' },
|
|
],
|
|
|
|
tokenizer: {
|
|
root: [
|
|
{ include: '@whitespace' },
|
|
{ include: '@numbers' },
|
|
{ include: '@strings' },
|
|
[/@[a-zA-Z_]\w*/, 'tag'],
|
|
[/\.\.\./, 'keyword'],
|
|
[/->/, 'delimiter'],
|
|
{ include: '@operators' },
|
|
[/[,:.;=]/, 'delimiter'],
|
|
[/[{}[\]()]/, '@brackets'],
|
|
[
|
|
/[a-zA-Z_]\w*/,
|
|
{
|
|
cases: {
|
|
'@keywords': 'keyword',
|
|
'@builtins': 'support.function',
|
|
'@default': 'identifier',
|
|
},
|
|
},
|
|
],
|
|
],
|
|
|
|
// Deal with white space, including single and multi-line comments
|
|
whitespace: [
|
|
[/\s+/, 'white'],
|
|
[/(^#.*$)/, 'comment'],
|
|
[/'''/, 'string', '@endDocString'],
|
|
[/"""/, 'string', '@endDblDocString'],
|
|
],
|
|
endDocString: [
|
|
[/[^']+/, 'string'],
|
|
[/\\'/, 'string'],
|
|
[/'''/, 'string', '@popall'],
|
|
[/'/, 'string'],
|
|
],
|
|
endDblDocString: [
|
|
[/[^"]+/, 'string'],
|
|
[/\\"/, 'string'],
|
|
[/"""/, 'string', '@popall'],
|
|
[/"/, 'string'],
|
|
],
|
|
|
|
// Recognize binary, octal, hex, decimals, imaginary, and scientific notation
|
|
numbers: [
|
|
[/\b0[bB](0|1|_)+/, 'constant.numeric.bin'],
|
|
[/\b0[oO]([0-7]|_)+/, 'constant.numeric.oct'],
|
|
[/\b0[xX]([abcdef]|[ABCDEF]|\d|_)+/, 'constant.numeric.hex'],
|
|
[/\b([\d_]*\.)?[\d_]+([eE][+-]?[\d_]+)?[jJ]?/, 'constant.numeric'],
|
|
],
|
|
|
|
// Recognize strings, including those broken across lines with \ (but not without)
|
|
strings: [
|
|
[/'$/, 'string.escape', '@popall'],
|
|
[/([fF]|[fF][rR]|[rR][fF])?'/, 'string.escape', '@fStringBody'],
|
|
[/[rRuUbB]?'/, 'string.escape', '@stringBody'],
|
|
[/"$/, 'string.escape', '@popall'],
|
|
[/([fF]|[fF][rR]|[rR][fF])?"/, 'string.escape', '@dblFStringBody'],
|
|
[/[rRuUbB]?"/, 'string.escape', '@dblStringBody'],
|
|
],
|
|
fStringBody: [
|
|
[/([^\\'{}]|\{\{|\}\}(?!\}[^}]))+$/, 'string', '@popall'],
|
|
[/([^\\'{}]|\{\{|\}\}(?!\}[^}]))+/, 'string'],
|
|
[/\\./, 'string'],
|
|
[/'/, 'string.escape', '@popall'],
|
|
[/\\$/, 'string'],
|
|
[
|
|
/\{/,
|
|
{
|
|
token: 'delimiter.curly',
|
|
next: '@fStringReplacement',
|
|
nextEmbedded: pybricksMicroPythonId,
|
|
},
|
|
],
|
|
[/\}/, 'delimiter.curly'],
|
|
],
|
|
dblFStringBody: [
|
|
[/([^\\"{}]|\{\{|\}\}(?!\}[^}]))+$/, 'string', '@popall'],
|
|
[/([^\\"{}]|\{\{|\}\}(?!\}[^}]))+/, 'string'],
|
|
[/\\./, 'string'],
|
|
[/"/, 'string.escape', '@popall'],
|
|
[/\\$/, 'string'],
|
|
[
|
|
/\{/,
|
|
{
|
|
token: 'delimiter.curly',
|
|
next: '@fStringReplacement',
|
|
nextEmbedded: pybricksMicroPythonId,
|
|
},
|
|
],
|
|
[/\}/, 'delimiter.curly'],
|
|
],
|
|
stringBody: [
|
|
[/[^\\']+$/, 'string', '@popall'],
|
|
[/[^\\']+/, 'string'],
|
|
[/\\./, 'string'],
|
|
[/'/, 'string.escape', '@popall'],
|
|
[/\\$/, 'string'],
|
|
],
|
|
dblStringBody: [
|
|
[/[^\\"]+$/, 'string', '@popall'],
|
|
[/[^\\"]+/, 'string'],
|
|
[/\\./, 'string'],
|
|
[/"/, 'string.escape', '@popall'],
|
|
[/\\$/, 'string'],
|
|
],
|
|
fStringReplacement: [
|
|
[/\}/, { token: '@rematch', next: '@pop', nextEmbedded: '@pop' }],
|
|
],
|
|
|
|
operators: [
|
|
[
|
|
/(\*\*|\/\/|<<|>>|:=|<=|>=|==|!=|\+=|-=|\*-|\/=|\.\.=|%=|@=|&=|\|=|\^=|>>=|<<=|\*\*=|[+\-*/%@&|^~<>])/,
|
|
'keyword.operator',
|
|
],
|
|
],
|
|
|
|
attributes: [
|
|
[/\b/, '@pop'],
|
|
[/[a-zA-Z_]\w*/, 'attribute'],
|
|
],
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Creates a new template for the given parameters.
|
|
*
|
|
* @param hubClassName The hub class name, e.g. `"MoveHub"`.
|
|
* @param deviceClassNames A list of device class names, e.g. `["Motor"]`.
|
|
* @returns The template.
|
|
*/
|
|
function createTemplate(hubClassName: string, deviceClassNames: string[]): string {
|
|
return `from pybricks.hubs import ${hubClassName}
|
|
from pybricks.pupdevices import ${deviceClassNames.join(', ')}
|
|
from pybricks.parameters import Button, Color, Direction, Port, Side, Stop
|
|
from pybricks.robotics import DriveBase
|
|
from pybricks.tools import wait, StopWatch
|
|
|
|
hub = ${hubClassName}()
|
|
|
|
`;
|
|
}
|
|
|
|
type HubLabel =
|
|
| 'movehub'
|
|
| 'cityhub'
|
|
| 'technichub'
|
|
| 'inventorhub'
|
|
| 'primehub'
|
|
| 'essentialhub';
|
|
|
|
const templateSnippets: Array<
|
|
Required<
|
|
Pick<monaco.languages.CompletionItem, 'label' | 'documentation' | 'insertText'>
|
|
> & { label: HubLabel }
|
|
> = [
|
|
{
|
|
label: 'technichub',
|
|
documentation: 'Template for Technic hub program.',
|
|
insertText: createTemplate('TechnicHub', ['Motor']),
|
|
},
|
|
{
|
|
label: 'cityhub',
|
|
documentation: 'Template for City hub program.',
|
|
insertText: createTemplate('CityHub', ['DCMotor', 'Light']),
|
|
},
|
|
{
|
|
label: 'movehub',
|
|
documentation: 'Template for BOOST Move hub program.',
|
|
insertText: createTemplate('MoveHub', ['Motor', 'ColorDistanceSensor']),
|
|
},
|
|
{
|
|
label: 'inventorhub',
|
|
documentation: 'Template for MINDSTORMS Robot Inventor hub program.',
|
|
insertText: createTemplate('InventorHub', [
|
|
'Motor',
|
|
'ColorSensor',
|
|
'UltrasonicSensor',
|
|
]),
|
|
},
|
|
{
|
|
label: 'primehub',
|
|
documentation: 'Template for SPIKE Prime program.',
|
|
insertText: createTemplate('PrimeHub', [
|
|
'Motor',
|
|
'ColorSensor',
|
|
'UltrasonicSensor',
|
|
'ForceSensor',
|
|
]),
|
|
},
|
|
{
|
|
label: 'essentialhub',
|
|
documentation: 'Template for SPIKE Essential program.',
|
|
insertText: createTemplate('EssentialHub', [
|
|
'Motor',
|
|
'ColorSensor',
|
|
'ColorLightMatrix',
|
|
]),
|
|
},
|
|
];
|
|
|
|
/**
|
|
* Gets the template text for a Pybricks MicroPython file.
|
|
* @param hub The hub label.
|
|
*/
|
|
export function getPybricksMicroPythonFileTemplate(
|
|
hub: HubLabel | undefined,
|
|
): string | undefined {
|
|
return templateSnippets.find((t) => t.label === hub)?.insertText;
|
|
}
|
|
|
|
export const templateSnippetCompletions = <monaco.languages.CompletionItemProvider>{
|
|
provideCompletionItems: (model, position, _context, _token) => {
|
|
// templates snippets are only available on the first line
|
|
if (position.lineNumber !== 1) {
|
|
return undefined;
|
|
}
|
|
|
|
const range = {
|
|
startLineNumber: position.lineNumber,
|
|
startColumn: 1,
|
|
endLineNumber: position.lineNumber,
|
|
endColumn: position.column,
|
|
};
|
|
|
|
const textUntilPosition = model.getValueInRange(range);
|
|
|
|
const items = templateSnippets
|
|
.filter((x) => x.label.startsWith(textUntilPosition))
|
|
.map<monaco.languages.CompletionItem>((x) => ({
|
|
detail: x.insertText,
|
|
kind: <monaco.languages.CompletionItemKind.Snippet>27,
|
|
range,
|
|
...x,
|
|
}));
|
|
|
|
if (!items) {
|
|
return undefined;
|
|
}
|
|
|
|
return { suggestions: items };
|
|
},
|
|
};
|
|
|
|
// old snippets from ace editor
|
|
// eslint-disable-next-line
|
|
const _unused = `
|
|
snippet imp
|
|
import \${1:module}
|
|
snippet from
|
|
from \${1:package} import \${2:module}
|
|
# Module Docstring
|
|
snippet docs
|
|
'''
|
|
File: \${1:FILENAME:file_name}
|
|
Author: \${2:author}
|
|
Date: \${3:date}
|
|
Description: \${4}
|
|
'''
|
|
snippet wh
|
|
while \${1:condition}:
|
|
\${2:# TODO: write code...}
|
|
# dowh - does the same as do...while in other languages
|
|
snippet dowh
|
|
while True:
|
|
\${1:# TODO: write code...}
|
|
if \${2:condition}:
|
|
break
|
|
snippet with
|
|
with \${1:expr} as \${2:var}:
|
|
\${3:# TODO: write code...}
|
|
# New Class
|
|
snippet cl
|
|
class \${1:ClassName}(\${2:object}):
|
|
"""\${3:docstring for $1}"""
|
|
def __init__(self, \${4:arg}):
|
|
\${5:super($1, self).__init__()}
|
|
self.$4 = $4
|
|
\${6}
|
|
# New Function
|
|
snippet def
|
|
def \${1:fname}(\${2:\`indent('.') ? 'self' : ''\`}):
|
|
"""\${3:docstring for $1}"""
|
|
\${4:# TODO: write code...}
|
|
snippet deff
|
|
def \${1:fname}(\${2:\`indent('.') ? 'self' : ''\`}):
|
|
\${3:# TODO: write code...}
|
|
# New Method
|
|
snippet defs
|
|
def \${1:mname}(self, \${2:arg}):
|
|
\${3:# TODO: write code...}
|
|
# New Property
|
|
snippet property
|
|
@property
|
|
def \${1:pname}():
|
|
\${2:return self._$1}
|
|
# Ifs
|
|
snippet if
|
|
if \${1:condition}:
|
|
\${2:# TODO: write code...}
|
|
snippet el
|
|
else:
|
|
\${1:# TODO: write code...}
|
|
snippet ei
|
|
elif \${1:condition}:
|
|
\${2:# TODO: write code...}
|
|
# For
|
|
snippet for
|
|
for \${1:item} in \${2:items}:
|
|
\${3:# TODO: write code...}
|
|
# Lambda
|
|
snippet ld
|
|
\${1:var} = lambda \${2:vars} : \${3:action}
|
|
snippet .
|
|
self.
|
|
snippet try Try/Except
|
|
try:
|
|
\${1:# TODO: write code...}
|
|
except \${2:Exception} as \${3:e}:
|
|
\${4:raise $3}
|
|
snippet try Try/Except/Else
|
|
try:
|
|
\${1:# TODO: write code...}
|
|
except \${2:Exception} as \${3:e}:
|
|
\${4:raise $3}
|
|
else:
|
|
\${5:# TODO: write code...}
|
|
snippet try Try/Except/Finally
|
|
try:
|
|
\${1:# TODO: write code...}
|
|
except \${2:Exception} as \${3:e}:
|
|
\${4:raise $3}
|
|
finally:
|
|
\${5:# TODO: write code...}
|
|
snippet try Try/Except/Else/Finally
|
|
try:
|
|
\${1:# TODO: write code...}
|
|
except \${2:Exception} as \${3:e}:
|
|
\${4:raise $3}
|
|
else:
|
|
\${5:# TODO: write code...}
|
|
finally:
|
|
\${6:# TODO: write code...}
|
|
snippet "
|
|
"""
|
|
\${1:doc}
|
|
"""
|
|
`;
|