Sarrus1 / sourcepawn-studio

VSCode extension for SourcePawn scripting
https://sarrus1.github.io/sourcepawn-studio/
MIT License
140 stars 22 forks source link

Support for AMXXPawn #280

Closed wopox1337 closed 1 year ago

wopox1337 commented 1 year ago

What do you want to see added to the extension?

I suggest adding support for AMXXPawn. The syntax is 95% similar to SourcePawn of the old syntax version.

I am attaching a slightly corrected tmLanguage for AMXXPawn (instead of the terrible) and can provide a Pull Request to add support.

For a start, just adding support for opening .sma files with your extension would be nice.

How would it help you?

There is no need to create a separate extension for AMXXPawn, which will not find support because of the lack of community and author activity.

The existing extension (https://github.com/rsKliPPy/amxxpawn-language) is long abandoned by the author, has many bugs, which will not be fixed, unfortunately...

How often would you use this feature?

The AMXXPawn community would appreciate "you" taking a little functionality like AMXXPawn support "under the wing". <3

I am attaching the corrected (there are some minor problems, but not critical) file .tmLanguage

amxxpawn.tmLanguage.json It is based on `sourcepawn.tmLanguage.json` with minor corrections. ```JSON { "$schema":"https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", "name":"AMXXPawn", "scopeName":"source.amxxpawn", "patterns":[ { "include":"#line_continuation_character" }, { "include":"#literals" }, { "include":"#comments" }, { "include":"#operators" }, { "include":"#preproc" }, { "comment":"type cast", "match":"(\\w+)\\:\\s*(\\w+)", "captures":{ "1":{ "patterns":[ { "include":"#type" } ] }, "2":{ "patterns":[ { "include":"#literals" }, { "include":"#variable_name" } ] } } }, { "comment":"Scope access", "match":"(\\w+)\\:\\:(\\w*)", "captures":{ "1":{ "patterns":[ { "include":"#type_name" } ] }, "2":{ "patterns":[ { "include":"#variable_name" } ] } } }, { "comment":"Control statements", "name":"keyword.control.statement.amxxpawn", "match":"\\b(?:if|else|for|while|do|switch|case|default|return|break|goto|continue)\\b" }, { "include":"#function-declaration" }, { "include":"#other-keywords" }, { "include":"#enum" }, { "include":"#variable-declaration" }, { "include":"#function-call" }, { "match":"\\b(\\w+)\\b", "captures":{ "1":{ "patterns":[ { "include":"#variable_name" } ] } } } ], "repository":{ "enum":{ "begin":"(enum)\\s+(?:(\\w+(?:\\:)?)?(?:\\s*(\\([^\\(]*\\)))?)?", "beginCaptures":{ "1":{ "patterns":[ { "include":"#keywords" } ] }, "2":{ "patterns":[ { "include":"#type_name" } ] }, "3":{ "patterns":[ { "include":"#literals" } ] } }, "end":"(?<=\\})", "patterns":[ { "include":"#literals" }, { "include":"#comments" }, { "include":"#preproc" }, { "match":"(\\w+)", "captures":{ "1":{ "patterns":[ { "include":"#variable_name" } ] } } } ] }, "function-call":{ "comment":"Function call", "match":"\\b([A-Za-z_][A-Za-z0-9_]*)\\s*\\(", "captures":{ "1":{ "name":"entity.name.function.function_call.amxxpawn" } } }, "other-keywords":{ "comment":"other-keywords", "name":"keyword.amxxpawn", "match":"\\b(?:forward|native|public|const|stock|sizeof|tagof|operator|static)\\b" }, "parameters":{ "patterns":[ { "include":"#keywords" }, { "include":"#literals" }, { "include":"#parameter-declaration" } ] }, "array-indexed-access":{ "patterns":[ { "match":"\\[([^\\]]+)\\]", "captures":{ "1":{ "patterns":[ { "include":"#literals" }, { "include":"#other-keywords" }, { "include":"#function-call" }, { "include":"#variable_name" } ] } } } ] }, "parameter-declaration":{ "patterns":[ { "match":"(?:(\\w+)\\s*\\:\\s*)?(\\w+|\\.{3})\\s*((?:\\[[^\\]]*\\]\\s*)+)?", "captures":{ "1":{ "patterns":[ { "include":"#type" } ] }, "2":{ "patterns":[ { "include":"#variable_name" } ] }, "3":{ "patterns":[ { "include":"#array-indexed-access" } ] } } } ] }, "type":{ "patterns":[ { "match":"(_|bool|any|Float)|(\\w+)", "captures":{ "1":{ "name":"storage.type.built-in.primitive.amxxpawn" }, "2":{ "patterns":[ { "include":"#type_name" } ] } } } ] }, "function-declaration":{ "patterns":[ { "begin":"(?:(stock|public|static)\\s+)?(?:(native|forward)\\s+)?(?:(\\w+)\\:)?\\s*([a-zA-Z_]\\w*)\\(", "beginCaptures":{ "1":{ "patterns":[ { "include":"#keywords" } ] }, "2":{ "patterns":[ { "include":"#keywords" } ] }, "3":{ "patterns":[ { "include":"#type" } ] }, "4":{ "name":"entity.name.function.amxxpawn" } }, "end":"\\)", "patterns":[ { "include":"#parameters" }, { "include":"#comments" }, { "include":"#preproc" } ] } ] }, "operators":{ "patterns":[ { "name":"keyword.operator.amxxpawn", "match":"%|&|\\*|/(?!\\*|/)|\\+|\\-|~|=|<|>|!|\\||\\?|:|\\^" } ] }, "variable-declaration":{ "patterns":[ { "match":"(?:(stock|public)\\s+)?((?:(?:const|static)\\s+)*)(new)\\s+(?:(\\w+)\\:\\s*)?(\\w+)(\\s*(?:\\[[^\\]]*\\]\\s*)*)", "captures":{ "1":{ "patterns":[ { "include":"#keywords" } ] }, "2":{ "patterns":[ { "include":"#keywords" } ] }, "3":{ "patterns":[ { "include":"#keywords" } ] }, "4":{ "patterns":[ { "include":"#type" } ] }, "5":{ "patterns":[ { "include":"#variable_name" } ] }, "6":{ "patterns":[ { "include":"#array-indexed-access" } ] } } } ] }, "keywords":{ "patterns":[ { "match":"\\b(public|stock)\\b", "name":"keyword.visibility.amxxpawn" }, { "match":"\\b(const|static|native|forward)\\b", "name":"keyword.storage_class.amxxpawn" }, { "match":"\\b(new)\\b", "name":"keyword.variable_declarator.amxxpawn" }, { "match":"\\b(enum)\\b", "name":"keyword.enum.amxxpawn" } ] }, "literals":{ "patterns":[ { "include":"#boolean-literal" }, { "include":"#null-literal" }, { "include":"#numeric-literal" }, { "include":"#string-literal" }, { "include":"#other-literal" } ] }, "boolean-literal":{ "patterns":[ { "match":"\\b(?:true|false)\\b", "name":"constant.language.boolean.amxxpawn" } ] }, "null-literal":{ "patterns":[ { "match":"\\bnull\\b", "name":"constant.language.amxxpawn" } ] }, "other-literal":{ "patterns":[ { "match":"\\b(?:EOS,cellbits,cellmin,cellmax,charmin,charmax,ucharmax,__Pawn,__LINE__,debug)\\b", "name":"constant.language.amxxpawn" } ] }, "numeric-literal":{ "patterns":[ { "comment":"Float literal", "match":"[0-9]+\\.[0-9]+", "name":"constant.numeric.float.amxxpawn" }, { "comment":"Binary literal", "match":"\\b0b[0-1]+\\b", "name":"constant.numeric.amxxpawn" }, { "comment":"Octodecimal literal", "match":"\\b0o[0-7]+\\b", "name":"constant.numeric.amxxpawn" }, { "comment":"Hexadecimal literal", "match":"\\b(0x(?:(?:(?:[0-9a-fA-F]{2}_?)+)|(?:[0-9a-fA-F]+)))\\b", "name":"constant.numeric.amxxpawn" }, { "comment":"Integer literal", "match":"\\b((?:\\d|_)+)\\b", "name":"constant.numeric.integer.amxxpawn" }, { "comment":"Invalid literal", "match":"\\b\\d+\\w+\\b", "name":"invalid.illegal.constant.amxxpawn" } ] }, "string-literal":{ "patterns":[ { "begin":"\"", "beginCaptures":{ "0":{ "name":"punctuation.definition.string.begin.amxxpawn" } }, "end":"(\")|((?:[^\\\\\\n])$)", "endCaptures":{ "1":{ "name":"punctuation.definition.string.end.amxxpawn" }, "2":{ "name":"invalid.illegal.newline.amxxpawn" } }, "name":"string.quoted.double.amxxpawn", "patterns":[ { "include":"#string_escaped_char" }, { "include":"#string_format_char" }, { "include":"#string_placeholder" }, { "include":"#line_continuation_character" } ] }, { "begin":"'", "beginCaptures":{ "0":{ "name":"punctuation.definition.string.begin.amxxpawn" } }, "end":"(\\')|((?:[^\\\\\\n])$)", "endCaptures":{ "1":{ "name":"punctuation.definition.string.end.amxxpawn" }, "2":{ "name":"invalid.illegal.newline.amxxpawn" } }, "name":"string.quoted.single.c", "patterns":[ { "include":"#string_escaped_char" }, { "include":"#string_format_char" }, { "include":"#string_placeholder" }, { "include":"#line_continuation_character" } ] } ] }, "line_continuation_character":{ "patterns":[ { "match":"(\\\\)\\n", "captures":{ "1":{ "name":"constant.character.escape.line-continuation.amxxpawn" } } } ] }, "string_escaped_char":{ "patterns":[ { "match":"\\\\(?:[abefnrt'\"\\\\]|(?:x[a-zA-Z0-9]{0,2}|\\d+);?)", "name":"constant.character.escape.amxxpawn" }, { "match":"\\\\.", "name":"invalid.illegal.unknown-escape.amxxpawn" } ] }, "string_format_char":{ "name":"constant.character.format.amxxpawn", "match":"%(?:a|A|b|B|c|C|d|D|e|F|g|G|h|H|I|j|m|M|n|i|p|r|R|S|t|T|u|U|V|u|w|W|x|X|y|Y|z|Z|f|L|N|s|T|t|%|(?:\\d+)?\\.?\\d*(?:b|d|i|u|f|s|X|x))" }, "type_name":{ "patterns":[ { "match":"\\b\\d\\b", "name":"constant.numeric.integer.amxxpawn" }, { "match":"[a-zA-Z_]\\w*", "name":"support.type.core.amxxpawn" }, { "match":"([0-9])(\\w*)", "captures":{ "1":{ "name":"invalid.illegal.constant.amxxpawn" }, "2":{ "name":"support.type.core.amxxpawn" } } } ] }, "variable_name":{ "patterns":[ { "match":"\\b\\d\\b", "name":"constant.numeric.integer.amxxpawn" }, { "match":"[a-zA-Z_]\\w*", "name":"variable.amxxpawn" }, { "match":"([0-9])(\\w*)", "captures":{ "1":{ "name":"invalid.illegal.constant.amxxpawn" }, "2":{ "name":"variable.amxxpawn" } } } ] }, "preproc":{ "patterns":[ { "match":"(\\#include|\\#tryinclude)\\s*((?:\\<|\").+(?:\\>|\"))", "name":"meta.include.amxxpawn", "captures":{ "1":{ "name":"keyword.control.amxxpawn" }, "2":{ "name":"string.amxxpawn" } } }, { "match":"(\\#pragma)\\s+(.+?(?=//))", "name":"meta.pragma.line-comment.amxxpawn", "captures":{ "1":{ "name":"keyword.control.pragma.amxxpawn" }, "2":{ "name":"entity.other.attribute-name.amxxpawn" } } }, { "match":"(\\#pragma)\\s+(deprecated)(.*)", "name":"meta.pragma.deprecated.amxxpawn", "captures":{ "1":{ "name":"keyword.control.pragma.amxxpawn" }, "2":{ "name":"entity.other.attribute-name.amxxpawn" }, "3":{ "name":"string.deprecated.amxxpawn" } } }, { "match":"(\\#pragma)\\s+([A-Za-z _0-9]+)", "name":"meta.pragma.block-comment.amxxpawn", "captures":{ "1":{ "name":"keyword.control.pragma.amxxpawn" }, "2":{ "name":"entity.other.attribute-name.amxxpawn" } } }, { "match":"(\\#define)\\s*(\\w*)", "name":"meta.define.amxxpawn", "captures":{ "1":{ "name":"keyword.control.define.amxxpawn" }, "2":{ "name":"meta.preprocessor.macro.amxxpawn" } } }, { "match":"(\\#undef)\\s*(\\w*)", "name":"meta.undef.amxxpawn", "captures":{ "1":{ "name":"keyword.control.undef.amxxpawn" }, "2":{ "name":"meta.preprocessor.macro.amxxpawn" } } }, { "comment":"Preprocessor warning and error with trailing comment", "match":"(#\\b(?:warning|error)\\b)\\s*(.+?)(?=/(?:/|\\*))", "name":"meta.undef.amxxpawn", "captures":{ "1":{ "name":"keyword.control.misc.amxxpawn" }, "2":{ "name":"string.warning.amxxpawn" } } }, { "comment":"Preprocessor warning and error", "match":"(#\\b(?:warning|error)\\b)\\s*(.*)", "name":"meta.undef.amxxpawn", "captures":{ "1":{ "name":"keyword.control.misc.amxxpawn" }, "2":{ "name":"string.warning.amxxpawn" } } }, { "comment":"Preprocessor keywords", "match":"#\\b(if|else|endif|emit|deprecated|undef|endinput|endscript|assert|define|file)\\b\\s*", "name":"keyword.control.misc.amxxpawn" }, { "comment":"Preprocessor defined", "match":"(defined)\\s+([A-Za-z_]\\w*)", "captures":{ "1":{ "name":"meta.preprocessor.conditional.amxxpawn" }, "2":{ "name":"meta.preprocessor.macro.amxxpawn" } } } ] }, "comments":{ "patterns":[ { "match":"\\/\\/.*", "name":"comment.amxxpawn" }, { "begin":"/\\*", "captures":{ "0":{ "name":"comment.amxxpawn" } }, "end":"\\*/", "name":"comment.block.amxxpawn" } ] } } } ```

also

amxxpawn.snippets.json ```JSON { "for loop": { "prefix": "for", "body": [ "for (new ${1|i,client,player|} = ${2:0}; $1 <= ${3:MaxClients}; $1++)", "{", "\t${4:}", "}" ], "description": "for loop" }, "while loop": { "prefix": "while", "body": ["while (${1:})", "{", "\t${2:}", "}"] }, "do while loop": { "prefix": "do", "body": ["do", "{", "\t${2:}", "}", "while(${1:})"] }, "if statement": { "prefix": "if", "body": ["if (${1:})", "{", "\t${2:}", "}"], "description": "if statement" }, "else statement": { "prefix": "else", "body": ["else", "{", "\t${1:}", "}"], "description": "else statement" }, "if else statement": { "prefix": "else if", "body": ["else if (${1:})", "{", "\t${2:}", "}"], "description": "else if statement" }, "include statement": { "prefix": "#include", "body": ["#include "], "description": "include" }, "pragma": { "prefix": "#pragma", "body": [ "#pragma ${1|amxlimit,codepage,compress,ctrlchar,deprecated,dynamic,library,reqlib,reqclass,loadlib,explib,expclass,defclasslib,reqlib,reqclass,loadlib,explib,expclass,defclasslib,pack,rational,semicolon,tabsize,align,unused,showstackusageinfo|}" ], "description": "pragma" }, "define": { "prefix": "#define", "body": ["#define ${1:defineName} ${2:defineValue}"], "description": "define" }, "template": { "prefix": "!!template", "body": [ "#include ", "", "", "public stock const PluginName[] = \"\";", "public stock const PluginVersion[] = \"\";", "public stock const PluginAuthor[] = \"\";", "public stock const PluginURL[] = \"\";", "public stock const PluginDescription[] = \"\";", "", "public plugin_init()", "{", "", "}" ], "description": "Plugin template" }, "descriptive comment": { "prefix": "!!description", "body": [ "/*----------------------------------------------", "-------------------${1:Description}------------------", "----------------------------------------------*/" ], "description": "Code description" } } ```

image

wopox1337 commented 1 year ago

for future installAMXX.ts: AMXModX:

Sarrus1 commented 1 year ago

Hello!

I am favorable to the idea, but I have a few questions to help me decide which approach would be ideal:

  1. If you rename all the extensions of the files in a project to .sp instead of .sma, how well does my extension work? I have zero experience with AMXModX.
  2. I am currently migrating the extension to use SourcePawn-LSP instead of the current code. Wouldn't it be better to create a fork of the LSP project once it's mature enough, and just build another VSCode extension, separate from AMXModX?
  3. Would somebody be willing to maintain it a bit? I'm willing to help with understanding the code etc.
wopox1337 commented 1 year ago
    • ✅ All the syntax highlighting functionality works fine, except for ctrchar, which in amxmodx defaults to '^' (not '\' like in sourcepawn). And it doesn't break the highlighting in any way. image

    • ✅ The parser successfully retrieves function data from amxmodx/include/*.inc about all basic functions as well as constants. image image

    • ✅ The syntax highlighting in *.inc files also works fine. image

    • ✔️ The tag with the construction ... - does not define as a type. (corrected by me in attached .tmLanguage)
      image

  1. Do you mean separately from sourcepawn? I unfortunately don't have enough free time to support a separate LSP, on Rust, I doubt the community can help with that at the moment. (there is a stagnation in developer activity and interest for GoldSrc, but that's not surprising).

Yes, technically it would be right to separate, however, given the circumstances, I don't see a more adequate outcome than integrating a stagnant AMXXPawn (not updated by developers) to your project.

  1. Helping with the development and integration of a TypeScript backend is much easier than Rust (in my opinion). Therefore, more people will be able to help support the project, however, I could be wrong about that.

Specifically, I - do not have enough experience in Rust to do support at this time. However, would be able to help with integration to existing TypeScript code. I understand the importance of separating the LSP and developing it in a high-performance programming language like Rust, but "what we have is what we have".

Sarrus1 commented 1 year ago

Given all the screenshots in 1, it seems that the extension could easily support AMXModX, so that's great.

Regarding 2 and 3 and maintainability, you are entirely right if we separate the projects, it's likely to get abandoned.

Here is what I propose:

I prefer to finish my migration of sourcepawn_lsp first, and then deploy this new VSCode extension amxmodx-vscode.

What do you think?

wopox1337 commented 1 year ago

Migrating to the Rust LSP is a great solution. Rust is better suited for demanding tasks like `tree-sitter' and features related to auto complete, go to definition, or documentation on hover.

Adding amxxpawn support mode to sourcepawn_LSP for the "evolved" sourcepawn (from amxxpawn) - seems more than reasonable, for at root have similar structures, visual similarity.

Creating a separate extension with SP_LSP support (amxxpawn mode), and implementing "VSCode goodies" on the frontend of the VSCode extension is a good idea. JS (TS) is not complicated, for support of features like 'load compiler from site', even more convenient.

In simple words, I agree with your solutions completely.

Sarrus1 commented 1 year ago

Great! I will do my best to implement this after I am done migrating to the sourcepawn_lsp.

wopox1337 commented 1 year ago

On the other hand, having an abandoned amxxpawn extension would be just as bad as implementing the same functionality on two extensions that are literally "working on the same thing". The new syntax can hardly be considered a 'major change'.

In terms of supporting an extension that is not the most popular, it would be easier to make amxxpawn support in one extension. Make a simple setup with a pawn revision adapter (GoldSrc: amxxpawn, Source: sourcepawn, optional SA:MP: pawno).

They all have very similar syntax, differing only in the revision of one programming language and in essence have little difference.

Settings in VSCode can be overridden at the project level, which will be handy for customization.

It looks harder to implement, but will be much easier to support later.

This way a single VSCode extension will have more reach, and as a consequence, more interest and possibly more support from the community. ╰(°▽°)╯

P.S: The LSP is definitely left on Rust as an external solution, which can function even outside of VSCode.

Sarrus1 commented 1 year ago

Moving this here: https://github.com/Sarrus1/amxxpawn-vscode