Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2955bdb
Extract type connectors
FireChickenProductivity Jan 8, 2026
8b6888e
Extract generic type parameter
FireChickenProductivity Jan 8, 2026
b706b4a
Extract type continuation
FireChickenProductivity Jan 8, 2026
6aeb5e4
Provide generic data structure capture
FireChickenProductivity Jan 8, 2026
b2f3262
Provide generic type parameter arguments capture
FireChickenProductivity Jan 8, 2026
403d686
Slightly reduce dependency on language specific information
FireChickenProductivity Jan 8, 2026
174d967
Simplify matching logic
FireChickenProductivity Jan 8, 2026
7b7b0c7
Extract type parameter argument formatting
FireChickenProductivity Jan 8, 2026
15c56c1
Support python generic typing
FireChickenProductivity Jan 8, 2026
a3b602b
Support or
FireChickenProductivity Jan 8, 2026
593d33f
Improve documentation
FireChickenProductivity Jan 8, 2026
c4c6e7f
Refactor language specific overrides
FireChickenProductivity Jan 8, 2026
5f75121
Improve formatting
FireChickenProductivity Jan 8, 2026
2bbb2d9
Improve readability
FireChickenProductivity Jan 8, 2026
4154485
Remove unused import
FireChickenProductivity Jan 8, 2026
9258613
Provide examples
FireChickenProductivity Jan 8, 2026
2ade280
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 8, 2026
0b8fa24
Document better and improve consistency
FireChickenProductivity Jan 8, 2026
bc1dcca
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 8, 2026
0ddc22c
Use typing compatible with 3.11
FireChickenProductivity Jan 8, 2026
9e61f3f
Merge branch 'main' into python-generic-types
nriley Jan 10, 2026
0ed4ad9
Merge branch 'main' into python-generic-types
FireChickenProductivity Jan 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 18 additions & 80 deletions lang/java/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from talon import Context, Module, actions, settings

from ...core.described_functions import create_described_insert_between
from ..tags.generic_types import format_type_parameter_arguments
from ..tags.operators import Operators

ctx = Context()
Expand Down Expand Up @@ -144,106 +145,43 @@ def public_camel_case_format_variable(variable: str):
# we plan on abstracting out from the specific implementations into a general grammar


@mod.capture(rule="{user.java_boxed_type} | <user.text>")
def java_type_parameter_argument(m) -> str:
@ctx.capture(
"user.generic_type_parameter_argument", rule="{user.java_boxed_type} | <user.text>"
)
def generic_type_parameter_argument(m) -> str:
"""A Java type parameter for a generic data structure"""
with suppress(AttributeError):
return m.java_boxed_type
return public_camel_case_format_variable(m.text)


@mod.capture(rule="[type] {user.java_generic_data_structure} | type <user.text>")
def java_generic_data_structure(m) -> str:
@ctx.capture(
"user.generic_data_structure",
rule="[type] {user.java_generic_data_structure} | type <user.text>",
)
def generic_data_structure(m) -> str:
"""A Java generic data structure that takes type parameter arguments"""
with suppress(AttributeError):
return m.java_generic_data_structure
return public_camel_case_format_variable(m.text)


class GenericTypeConnector(Enum):
AND = auto()
OF = auto()
DONE = auto()


@mod.capture(rule="done")
def java_generic_type_connector_done(m) -> GenericTypeConnector:
"""Denotes ending a nested generic type"""
return GenericTypeConnector.DONE


@mod.capture(rule="and|of|<user.java_generic_type_connector_done>")
def java_generic_type_connector(m) -> GenericTypeConnector:
"""Determines how to put generic type parameters together"""
with suppress(AttributeError):
return m.java_generic_type_connector_done
return GenericTypeConnector[m[0].upper()]


@mod.capture(
rule="<user.java_generic_type_connector> <user.java_type_parameter_argument> [<user.java_generic_type_connector_done>]+"
)
def java_generic_type_continuation(m) -> list[Union[GenericTypeConnector, str]]:
"""A generic type parameter that goes after the first using connectors"""
result = [m.java_generic_type_connector, m.java_type_parameter_argument]
with suppress(AttributeError):
dones = m.java_generic_type_connector_done_list
result.extend(dones)
return result


@mod.capture(rule="<user.java_generic_type_continuation>+")
def java_generic_type_additional_type_parameters(
m,
) -> list[Union[GenericTypeConnector, str]]:
"""Type parameters for a generic data structure after the first one"""
result = []
for continuation in m.java_generic_type_continuation_list:
result.extend(continuation)
return result


def is_immediately_after_nesting_exit(pieces: list[str]) -> bool:
return len(pieces) >= 1 and pieces[-1] == ">"


@mod.capture(
rule="<user.java_type_parameter_argument> [<user.java_generic_type_additional_type_parameters>]"
@ctx.capture(
"user.generic_type_parameter_arguments",
rule="<user.generic_type_parameter_argument> [<user.generic_type_additional_type_parameters>]",
)
def java_type_parameter_arguments(m) -> str:
def generic_type_parameter_arguments(m) -> str:
"""Formatted Java type parameter arguments"""
parameters = [m.java_type_parameter_argument]
with suppress(AttributeError):
parameters.extend(m.java_generic_type_additional_type_parameters)
pieces = []
nesting: int = 0
for parameter in parameters:
if isinstance(parameter, str):
if is_immediately_after_nesting_exit(pieces):
pieces.append(", ")
pieces.append(parameter)
else:
match parameter:
case GenericTypeConnector.AND:
pieces.append(", ")
case GenericTypeConnector.OF:
pieces.append("<")
nesting += 1
case GenericTypeConnector.DONE:
pieces.append(">")
nesting -= 1
if nesting > 0:
pieces.append(">" * nesting)
return "".join(pieces)
return format_type_parameter_arguments(m, ", ", "<", ">")


@mod.capture(
rule="<user.java_generic_data_structure> of <user.java_type_parameter_arguments>"
rule="<user.generic_data_structure> of <user.generic_type_parameter_arguments>"
)
def java_generic_type(m) -> str:
"""A generic type with specific type parameters"""
parameters = m.java_type_parameter_arguments
return f"{m.java_generic_data_structure}<{parameters}>"
parameters = m.generic_type_parameter_arguments
return f"{m.generic_data_structure}<{parameters}>"


# End of unstable section
Expand Down
76 changes: 76 additions & 0 deletions lang/python/python.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import re
from contextlib import suppress

from talon import Context, Module, actions, settings

from ...core.described_functions import create_described_insert_between
from ..tags.generic_types import (
SimpleLanguageSpecificTypeConnector,
format_type_parameter_arguments,
)
from ..tags.operators import Operators

mod = Module()
Expand Down Expand Up @@ -103,6 +108,77 @@
for exception in exception_list
}

# This is not part of the long term stable API
# After we implement generics support for several languages,
# we plan on abstracting out from the specific implementations into a general grammar

mod.list(
"python_generic_type", desc="A python type that takes type parameter arguments"
)

# this should be moved to a talon-list file after this becomes stable
ctx.lists["user.python_generic_type"] = {
"callable": "Callable",
"dictionary": "dict",
"iterable": "Iterable",
"list": "list",
"optional": "Optional",
"set": "set",
"tuple": "tuple",
"union": "Union",
}


@ctx.capture(
"user.generic_type_parameter_argument", rule="<user.code_type> | [type] <user.text>"
)
def generic_type_parameter_argument(m) -> str:
"""A Python type parameter for a generic data structure"""
with suppress(AttributeError):
return m.code_type
return actions.user.formatted_text(m.text, "PUBLIC_CAMEL_CASE")


@ctx.capture(
"user.generic_data_structure",
rule="{user.python_generic_type} | [type] <user.text>",
)
def generic_data_structure(m) -> str:
"""A Python generic data structure that takes type parameter arguments"""
with suppress(AttributeError):
return m.python_generic_type
return actions.user.formatted_text(m.text, "PUBLIC_CAMEL_CASE")


@ctx.capture(
"user.generic_type_connector", rule="<user.common_generic_type_connector>|or"
)
def generic_type_connector(m) -> SimpleLanguageSpecificTypeConnector:
"""A Python specific type connector for union types"""
with suppress(AttributeError):
return m.common_generic_type_connector
return SimpleLanguageSpecificTypeConnector(" | ")


@ctx.capture(
"user.generic_type_parameter_arguments",
rule="<user.generic_type_parameter_argument> [<user.generic_type_additional_type_parameters>]",
)
def generic_type_parameter_arguments(m) -> str:
return format_type_parameter_arguments(m, ", ", "[", "]")


@mod.capture(
rule="<user.generic_data_structure> of <user.generic_type_parameter_arguments>"
)
def python_generic_type(m) -> str:
"""A generic type with specific type parameters"""
parameters = m.generic_type_parameter_arguments
return f"{m.generic_data_structure}[{parameters}]"


# End of unstable section

operators = Operators(
# code_operators_array
SUBSCRIPT=create_described_insert_between("[", "]"),
Expand Down
20 changes: 20 additions & 0 deletions lang/python/python.talon
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,23 @@ import <user.code_libraries>:
key(end enter)

from import: user.insert_snippet_by_name("importFromStatement")

# the generic type commands are currently unstable and may be subject to change

# examples of using the <user.python_generic_type> capture:
# "list of string" -> list[str]
# "list of string or integer" -> list[str | int]
# types can be nested with `of`:
# "list of optional of integer" -> list[Optional[int]]
# `and` can be used for multiple arguments:
# "tuple of integer and float" -> tuple[int, float]
# `done` can be used to exit a nesting:
# "tuple of optional of integer done string" -> tuple[Optional[int], str]
# user defined type names are capitalized
# "list of custom type" -> list[CustomType]
# saying `type` first refers to a custom type
# "type list of type integer" -> List[Integer]

type <user.python_generic_type>: insert(python_generic_type)
returns <user.python_generic_type>: insert(" -> {python_generic_type}")
is type <user.python_generic_type>: insert(": {python_generic_type}")
136 changes: 136 additions & 0 deletions lang/tags/generic_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# This functionality is unstable and subject to change
# we want to implement generic type support for several languages before finalizing the general abstraction

from contextlib import suppress
from dataclasses import dataclass
from enum import Enum, auto
from typing import Union

from talon import Module


class CommonTypeConnector(Enum):
"""A common type connector for connecting type arguments for a generic type"""

AND = auto()
OF = auto()
DONE = auto()


@dataclass(slots=True)
class SimpleLanguageSpecificTypeConnector:
"""A type connector that only requires inserting text with no other complexity,
e.g. Python's `|` for union types
"""

text: str


TypeConnector = Union[CommonTypeConnector, SimpleLanguageSpecificTypeConnector]

mod = Module()

# implement the following for a specific language


@mod.capture
def generic_type_parameter_argument(m) -> str:
"""A type parameter for a generic data structure. This should include standard types of a language and appropriate formatting of arbitrary text for user types"""
pass


@mod.capture
def generic_data_structure(m) -> str:
"""A generic data structure that takes type parameter arguments"""
pass


@mod.capture(
rule="<user.generic_type_parameter_argument> [<user.generic_type_additional_type_parameters>]"
)
def generic_type_parameter_arguments(m) -> str:
"""This combines type parameter arguments, connectors, and the containing type into a formatted string. This is usually formatted using format_type_parameter_arguments"""
pass


# end of language specific section


@mod.capture(rule="done")
def generic_type_connector_done(m) -> CommonTypeConnector:
"""Denotes ending a nested generic type"""
return CommonTypeConnector.DONE


@mod.capture(rule="and|of|<user.generic_type_connector_done>")
def common_generic_type_connector(m) -> CommonTypeConnector:
"""A common type connector for generic types"""
with suppress(AttributeError):
return m.generic_type_connector_done
return CommonTypeConnector[m[0].upper()]


@mod.capture(rule="<user.common_generic_type_connector>")
def generic_type_connector(m) -> TypeConnector:
"""A generic type connector for determining how to put type parameters together.
Override on a per language basis to add additional connectors.
"""
return m.common_generic_type_connector


@mod.capture(
rule="<user.generic_type_connector> <user.generic_type_parameter_argument> [<user.generic_type_connector_done>]+"
)
def generic_type_continuation(m) -> list[Union[TypeConnector, str]]:
"""A generic type parameter that goes after the first using connectors"""
result = [m.generic_type_connector, m.generic_type_parameter_argument]
with suppress(AttributeError):
dones = m.generic_type_connector_done_list
result.extend(dones)
return result


@mod.capture(rule="<user.generic_type_continuation>+")
def generic_type_additional_type_parameters(
m,
) -> list[Union[TypeConnector, str]]:
"""Type parameters for a generic data structure after the first one"""
result = []
for continuation in m.generic_type_continuation_list:
result.extend(continuation)
return result


def format_type_parameter_arguments(
m,
argument_separator: str,
generic_parameters_start: str,
generic_parameters_end: str,
) -> str:
"""Formats type parameter arguments for languages with simple generic typing"""
parameters = [m.generic_type_parameter_argument]
with suppress(AttributeError):
parameters.extend(m.generic_type_additional_type_parameters)
pieces = []
nesting: int = 0
is_immediately_after_nesting_exit = False
for parameter in parameters:
match parameter:
case CommonTypeConnector.AND:
pieces.append(argument_separator)
case CommonTypeConnector.OF:
pieces.append(generic_parameters_start)
nesting += 1
case CommonTypeConnector.DONE:
pieces.append(generic_parameters_end)
nesting -= 1
case SimpleLanguageSpecificTypeConnector():
pieces.append(parameter.text)
case str():
if is_immediately_after_nesting_exit:
pieces.append(argument_separator)
pieces.append(parameter)
is_immediately_after_nesting_exit = parameter == CommonTypeConnector.DONE
if nesting > 0:
pieces.append(generic_parameters_end * nesting)
return "".join(pieces)