diff --git a/core/file_extension/file_extension.py b/core/file_extension/file_extension.py index 1a75e76c42..340790e5cc 100644 --- a/core/file_extension/file_extension.py +++ b/core/file_extension/file_extension.py @@ -7,6 +7,7 @@ _file_extensions_defaults = { "dot pie": ".py", + "dot elixir": ".ex", "dot talon": ".talon", "dot mark down": ".md", "dot shell": ".sh", diff --git a/core/modes/language_modes.py b/core/modes/language_modes.py index 936c147b12..f069fd9600 100644 --- a/core/modes/language_modes.py +++ b/core/modes/language_modes.py @@ -20,6 +20,7 @@ "javascript": "js", "javascriptreact": "jsx", # "json": "json", + "elixir": "ex", "kotlin": "kt", "lua": "lua", "markdown": "md", diff --git a/lang/elixir/elixir.py b/lang/elixir/elixir.py new file mode 100644 index 0000000000..8387feccf8 --- /dev/null +++ b/lang/elixir/elixir.py @@ -0,0 +1,168 @@ +from talon import Context, Module, actions, settings + +ctx = Context() +mod = Module() +ctx.matches = r""" +code.language: elixir +""" + +# Elixir keywords and constructs +ctx.lists["user.code_keyword"] = { + "def": "def ", + "def p": "defp ", + "def module": "defmodule ", + "do": "do ", + "end": "end", + "if": "if ", + "else": "else ", + "cond": "cond ", + "case": "case ", + "when": "when ", + "f n": "fn ", + "receive": "receive ", + "after": "after ", + "try": "try ", + "catch": "catch ", + "rescue": "rescue ", + "raise": "raise ", + "with": "with ", + "unless": "unless ", + "import": "import ", + "alias": "alias ", + "require": "require ", + "use": "use ", +} + + +@ctx.action_class("user") +class UserActions: + def code_comment_line_prefix(): + actions.insert("# ") + + def code_operator_lambda(): + actions.auto_insert("->") + + def code_operator_assignment(): + actions.auto_insert(" = ") + + def code_operator_addition(): + actions.auto_insert(" + ") + + def code_operator_subtraction(): + actions.auto_insert(" - ") + + def code_operator_multiplication(): + actions.auto_insert(" * ") + + def code_operator_division(): + actions.auto_insert(" / ") + + def code_operator_equal(): + actions.auto_insert(" == ") + + def code_operator_not_equal(): + actions.auto_insert(" != ") + + def code_operator_greater_than(): + actions.auto_insert(" > ") + + def code_operator_greater_than_or_equal_to(): + actions.auto_insert(" >= ") + + def code_operator_less_than(): + actions.auto_insert(" < ") + + def code_operator_less_than_or_equal_to(): + actions.auto_insert(" <= ") + + def code_operator_and(): + actions.auto_insert(" and ") + + def code_operator_or(): + actions.auto_insert(" or ") + + def code_self(): + actions.auto_insert("self") + + def code_insert_true(): + actions.auto_insert("true") + + def code_insert_false(): + actions.auto_insert("false") + + def code_insert_null(): + actions.insert("nil") + + def code_insert_is_null(): + actions.insert(" == nil") + + def code_insert_is_not_null(): + actions.insert(" != nil") + + def code_state_if(): + actions.user.insert_between("if ", " do\nend") + + def code_state_else_if(): + actions.user.insert_between("else if ", " do\nend") + + def code_state_else(): + actions.insert("else\nend") + actions.key("enter") + + def code_state_case(): + actions.user.insert_between("case ", " do\nend") + + def code_state_for(): + actions.user.insert_between("for ", " do\nend") + + def code_state_while(): + actions.user.insert_between("while ", " do\nend") + + def code_define_class(): + # Elixir doesn't have classes, so this is not applicable + pass + + def code_state_return(): + # Elixir functions automatically return the last evaluated expression + pass + + def code_insert_function(text: str, selection: str): + text += f"({selection or ''})" + actions.user.paste(text) + actions.edit.left() + + def code_default_function(text: str): + result = "def {}".format( + actions.user.formatted_text( + text, settings.get("user.code_protected_function_formatter") + ) + ) + actions.user.code_insert_function(result, None) + + def code_public_function(text: str): + actions.user.code_default_function(text) + + def code_private_function(text: str): + """Inserts private function declaration""" + result = "defp {}".format( + actions.user.formatted_text( + text, settings.get("user.code_private_function_formatter") + ) + ) + actions.user.code_insert_function(result, None) + + def code_import_module(text: str): + actions.auto_insert("import ") + actions.insert(text) + + def code_alias_module(text: str): + actions.auto_insert("alias ") + actions.insert(text) + + def code_require_module(text: str): + actions.auto_insert("require ") + actions.insert(text) + + def code_use_module(text: str): + actions.auto_insert("use ") + actions.insert(text) diff --git a/lang/elixir/elixir.talon b/lang/elixir/elixir.talon new file mode 100644 index 0000000000..ee5052b6e5 --- /dev/null +++ b/lang/elixir/elixir.talon @@ -0,0 +1,40 @@ +code.language: elixir +- +tag(): user.code_functional +tag(): user.code_concurrent + +tag(): user.code_comment_line +tag(): user.code_data_bool +tag(): user.code_data_null +tag(): user.code_functions +tag(): user.code_keywords +tag(): user.code_libraries +tag(): user.code_operators_array +tag(): user.code_operators_assignment +tag(): user.code_operators_math +tag(): user.code_operators_lambda + +settings(): + user.code_private_function_formatter = "snake_case" + user.code_public_function_formatter = "snake_case" + user.code_private_variable_formatter = "snake_case" + user.code_public_variable_formatter = "snake_case" + +# Elixir-specific grammars +state def: "def " +state defp: "defp " +state if: "if " +state else: "else" +state case: "case " +state cond: "cond do" +state try: "try do" +state rescue: "rescue" +state after: "after" +state end: "end" + +op pipe: " |> " + +# Elixir-specific keywords and symbols +[state] raise {user.elixir_exception}: user.insert_between("raise ", "") + +[state] rescue {user.elixir_exception}: "rescue {elixir_exception}"