-
Notifications
You must be signed in to change notification settings - Fork 46
Smart completion
An [as of yet] unique feature of GMEdit is in being able to provide contextual syntax completion and syntax highlighting for variables and (2.3) methods.
The premise is as following: if GMEdit knows what a type of an expression is, it can offer you contextual auto-completion (e.g. only showing variables from a specific instance) and better error checking (e.g. warning about assigning a string into a numeric variable or using wrong argument types.
Types and fields are determined as following:
-
Local variables
Local variable types can be indicated usingvar v:T
shorthand syntax. -
Built-in functions and variables
GMEdit comes with type definitions (see/resources/app/api/shared/
) for majority of GMS2 functions and most GMS2 functions. -
User scripts and functions
Argument types can be indicated using@param
or shorthand syntax (function(a:T)
in GMS≥2.3,#args a:T
for older versions).
Return type can be indicated using@returns
or shorthand syntax (function(...)->ReturnType
) in GMS≥2.3. -
Object and struct variables
GMEdit will automatically pick up variable names declared at top-level (read: not inside{}
) within Create events and 2.3 constructors, including inheritance.
Variable types can be specified using@is
; additional variables can be indicated using@hint
or@implements
. "Implicit types" can be enabled in linter preferences to auto-derive non-ambiguous types. -
Global variables
GMEdit will automatically indexglobal.name
andglobalvar
declarations. Types can be similarly indicated using@is
. -
Macros
GMEdit is able to expand macros and derive types from code within. -
self
Type ofself
is automatically known in objects and constructors and objects but can be set using@self
.
(note: you can summon self-specific completions without self-prefix by typing a period.
out-of-context)
GMEdit has the following special built-in types:
-
any
: casts to and from any type.
If type of something is not known, it is assumed to beany
. -
void
: cannot be cast from or to.
Can be used to mark variables that should never be touched or as a function return value (or, well, lack thereof) -
bool
: boolean values (true/false).
As this is GML, can cast to/fromnumber
implicitly. -
number
: includes any numeric values, such as reals and int64s. -
int
: currently an alias fornumber
and provided for convenience. -
string
: string values and literals. -
undefined
: the built-inundefined
value is this type. -
array
,array<T>
: a generic array or an array of specified type accordingly.
You can castarray<int>
toarray
and vice versa, but notarray<int>
toarray<string>
, for example.
You can also useT[]
in place ofarray<T>
for convenience. -
ckarray<K, V>
,CustomKeyArray<K, V>
: an array with a specific key/index type.
This is good for cases when the key is technically an integer (e.g. a resource ID) but doesn't implicitly cast to it as far as type checking goes, e.g.var spriteData:ckarray<sprite, int> = ...; var a = spriteData[spr_somme]; // OK var b = spriteData[obj_some]; // shows a warning var b = spriteData[0]; // also shows a warning
-
ckstruct<K, V>
,CustomKeyStruct<K, V>
: same as above, but for structs (andstruct[$ key]
access). -
T?
(formallyNull<T>
): specified type orundefined
.
T
can be cast toT?
, but castingT?
back to value requiresas
- e.g.var v:?int;
..i = v as int
. -
ds_map<K, V>
,ds_list<T>
,ds_grid<T>
, etc.: built-in data structure types. -
specified_map<...field:type, defaultType>
: a map with known keys.
GMEdit uses this for different kinds ofasync_load
, but you can also use this yourself for JSON - e.g.specified_map<i:int, s:string, void>
would allowmap[?"i"]
andmap[?"s"]
(and type them accordingly) while forbidding any other key access. -
object
: any object/instance can be cast to this type.
Handy for type constraints! -
struct
: any struct-based value can be cast to this type. -
asset
: any resource (sprites, objects, etc.) can be cast to this type. -
type<T>
: a reference to a type.
If you have a@hint
-based type, e.g.referencingglobalvar Some; Some = function(a, b) constructor { // ... } /// @hint new Some(a, b) /// @hint Some.staticFunc(a, b)
Some
directly will have it typed astype<Some>
. -
A|B
or(A|B)
: either of types.
So, for example, you can donumber|string
, or even more types.
Parentheses are convenient for grouping - e.g. you can use(A|B|C)[]
to declare an array that contains values of types A, B, or C.
You can cast values of any of types to either-type, but casting from either-type to a specific type requires use ofas
.
Can also be usedeither<A, B...>
. -
function<argType1, argType2, ..., returnType>
: a function with a matching signature.
If a function returns nothing,returnType
can bevoid
.
For functions with trailing arguments, userest<type>
.
Examples:var _clamp:function<number, number, number> = clamp; var _min:function<rest<number>, number> = min; var _show_debug_message:function<any, void> = show_debug_message;
-
[A, B]
: a tuple - that is, a fixed-size array with per-index types.
Since arrays use less memory than structs, they can be beneficial for small sets of data.
Examples:Can also be used asvar v2:tuple<number, string> = [1, "hi!"]; v2[0] = ""; // will warn v2[2] = 0; // also will warn var v3:tuple<x:number, y:number, z:number> = [1, 2, 3]; // with field labels
tuple<A, B, ...>
;
If the last type isrest<something>
, the tuple will accept trailing values. -
tuple<enum_name>
or `enum_name: automatic tuple for enum items.
This complements type magic. Types for individual enum items can be defined as following:enum v_enum_tuple { an_int, /// @is {int} a_string, /// @is {string} sizeof }
-
buffer_auto_type
: a special type for buffer-related functions.
If your function has an argument typed asbuffer_type
, setting a subsequent argument's or return's type tobuffer_auto_type
will dynamically change it based on what type was passed in - e.g.int
forbuffer_s32
orstring
forbuffer_string
.GMEdit uses this for a lot of built-in/// @param {buffer} buf /// @param {buffer_type} type /// @param {buffer_auto_type} value function my_buffer_write(buf, type, value) { ... } // my_buffer_write(buf, buffer_s32, "hi") would show a warning /// @param {buffer} buf /// @param {buffer_type} type /// @returns {buffer_auto_type} value function my_buffer_read(buf, type) { ... } // x = my_buffer_read(buf, buffer_string) would show a warning
buffer_*
functions
Allows to explicitly cast an expression to a compatible type - such as casting int?
to int
or picking a specific one of either-types.
For example,
var sn:string|int = ...;
var i:int = is_string(sn) ? real(sn as string) : sn as int;
In saved file, this becomes a /*#as T*/
.
Allows to explicitly cast an expression to any
, bypassing type checks in cases where that might be necessary.
For example,
var s:string = argument0;
if (is_real(s)) s = string_format(cast s, 0, 3); // no warning
// ...
In saved file, this becomes a /*#cast*/
.
Allows to explicitly cast an expression to a type (including "incompatible" ones). Generally used in parenthesis, so typing
(cast buffer_read(b, buffer_s32) as obj_entity).
would show you auto-completion for variables from obj_entity
and check for errors accordingly.
Suppose you have obj_enemy
with the following Create event:
maxhealth = 10; // @is {number}
my_health = maxhealth; // @is {number}
my_target = noone; // @is {obj_entity}
// attack = function(target) {} // 2.3 method
After saving, if you were to type self.
anywhere in the object, you would only get the variables defined in Create event and the built-in variables rather than everything that you have in your project.
Similarly, if you were to type obj_enemy.
anywhere in the project, you would only get those same variables.
With above setup, if you were to write
var e:obj_enemy = instance_nearest(x, y, obj_enemy);
After saving, typing e.
would show you only the variables from obj_enemy
.
Note: if you are confident that you are writing good code, you can enable "Implicit types for local variables" in Preferences or Project Properties to have types auto-derived for variable declarations with initial values (var v = val
).
2.2: Suppose you had scr_enemy_ai
that would be called by obj_enemy
with the following
/// @self {obj_enemy}
self.my_target = instance_nearest(x, y, obj_player);
After saving, typing self.
would show you only the variables from obj_enemy
.
2.3 version (much the same, but JSDoc tags sit outside the functions now):
/// @self {obj_enemy}
function scr_enemy_ai() {
self.my_target = instance_nearest(x, y, obj_player);
}
Suppose you had a pair of functions that represent things that you might assign into constructors/objects:
/// @interface {IHorsable}
function scr_init_horsable() {
neigh = function(magnitude) { throw "not implemented!" }
}
/// @interface
function scr_twovars() {
oneVar = 1;
twoVar = 2;
}
(in 2.2, make that two scripts with /// @interface
inside the script and replace function(){} by another script)
You would then be able to mark them for inclusion in auto-completion and highlighting by doing the following in Create event of an object:
/// @implements {IHorsable}
scr_twovars(); /// @implements
myVar = "hi!";
Or, for constructors,
/// @implements {IHorsable}
function Some() constructor {
// ...
scr_twovars(); /// @implements
myVar = "hi!";
}
which would then show you myVar
, oneVar
, twoVar
, and neigh(magnitude)
when typing self.
.
- Like with most GMEdit features, you'll need to save (Ctrl+S) when adding/changing code that specifies types.
- For compatibility purposes, object references are not
object
ortype<obj_name>
, but justobj_name
. With almost allinstance_
functions taking either an object or an instance, this can only backfire ininstance_create[_depth|_layer]
(as you would be able to pass in an instance instead of an object). - When using
@hint A extends B
or constructor inheritance on types with parameters (@template
), child's first parameters must match up with parent's parameters.
- Smart auto-completion
- JSDoc tags (incl. additional ones)
- @hint tag (mostly 2.3)
- `vals: $v1 $v2` (template strings)
- #args (pre-2.3 named arguments)
- ??= (for pre-GM2022 optional arguments)
- ?? ?. ?[ (pre-GM2022 null-conditional operators)
- #lambda (pre-2.3 function literals)
- => (2.3+ function shorthands)
- #import (namespaces and aliases)
- v:Type (local variable types)
- #mfunc (macros with arguments)
- #gmcr (coroutines)