I agree it makes sense to accept a `newlines` regex as an option to 
`String.indent/2`, and have pushed an implementation supporting that to my 
branch for posterity!

> [The] Elixir codebase is actually a bit different. We don't indent 
consecutive or trailing newlines

My understanding is that this is an optimization over the direct approach 
that text editors normally take: indenting the whole document, then 
stripping trailing whitespace. Since we can't re.search over an algebra 
document, though, if we support indents in code generation we'd have to 
either implement the optimization within the algebra formatter when 
emitting new lines, implement the unoptimized approach with a second pass, 
or some other option. Neither bring me joy, which is an argument for not 
implementing, which is an argument for (IMO) just shipping the 
String.indent variant instead and letting the user provide a regex in post 
to perform the optimization themselves.

> I am potentially fine with adding an option to some of the code 
generation helpers we have, but I am not sure we need the function in 
standard library.

My thought here was that if we add String.indent, then we don't strictly 
need the option in the code generation helpers, so I started with that in 
my PR. Looking at the complexity of not just updating `IO.Algebra.format` 
to insert indentation mid-format, but also considering the complexity that 
would come from then needing to decide if IO.Algebra should be concerned 
with trailing whitespace, I'm definitely a fan of cutting scope.

Per the PR discussion, that leaves 2 decisions: 1) should this return the 
IO.data instead, and 2) should this proposal be approved or not, based on 
a) not really wanting to support a fairly minor helper vs b) the complexity 
of maintaining the gnarlier code-formatter implementation vs c) passing on 
both.
On Friday, January 9, 2026 at 1:42:01 AM UTC-6 José Valim wrote:

> Thanks for the proposal.
>
> I am potentially fine with adding an option to some of the code generation 
> helpers we have, but I am not sure we need the function in standard 
> library. A simple indent implementation could be written as:
>
> def indent(string, indent) do
>   indent <> String.replace(string, ~r/\r\n|\n/, & &1 <> indent)
> end
>
> Or even without a regex:
>
> def indent(string, indent) do
>   indent <> String.replace(string, ["\r\n", "\n"], & &1 <> indent)
> end
>
> Especially because I don't see a reason to pass anything but a string.
>
> However, you will find that most indent in Elixir codebase is actually a 
> bit different. We don't indent consecutive or trailing newlines, so we 
> would either need to add more options. If we have an official API, now we 
> need to support these different options, while instead you could just 
> change the one liner to use either ~r/(\r\n|\n)+/ or ~r/(\r\n|\n)+(?!$)/ 
> respectively for the desired results.
>
> On Thursday, January 8, 2026 at 1:52:19 AM UTC+1 [email protected] wrote:
>
>> This was common enough that it's an option in Sourceror.to_string/2 
>> <https://hexdocs.pm/sourceror/Sourceror.html#to_string/2>
>>
>> On Wed, Jan 7, 2026 at 9:43 PM Zach Daniel <[email protected]> wrote:
>>
>>> This is such a good idea. I do this a lot too and didn't think of it, 
>>> especially in library code. In strong support.
>>>
>>>
>>> On Wed, Jan 07, 2026 at 12:22 PM, Christopher Keele <[email protected]
>>> > wrote:
>>>
>> *Proposal:*
>>>>
>>>>
>>>>    1. Add support for indenting multi-line strings via *String.indent*:
>>>>    *defmodule String do*
>>>>    * @type indentation ::*
>>>>    * binary*
>>>>    * | Inspect.Algebra.t()*
>>>>    * | [{:spaces, non_neg_integer()}]*
>>>>    * | [{:tabs, non_neg_integer()}]*
>>>>    
>>>> * @spec indent(String.t(), indentation) :: String.t()*
>>>>    * def indent(string, indentation)*
>>>>    *end*
>>>>    2. Add support for indenting ast representations in 
>>>>    *Macro.to_string*:
>>>>    defmodule Macro do
>>>>    @type to_string_opt :: {:indent, String.indentation()}
>>>>    @spec to_string(Macro.t(), [to_string_opt()]) :: String.t()
>>>>    def to_string(string, options \\ [])
>>>>    def to_string(string, options)
>>>>    end
>>>>    3. Add support for indenting code in *Code.format_string!*:
>>>>    defmodule Code do
>>>>    @type format_opt :: 
>>>>    # ... |
>>>>    {:indent, String.indentation()}
>>>>    @spec format_string!(String.t(), [format_opt()]) :: iodata()
>>>>    def format_string!(string, opts \\[])
>>>>    end
>>>>    4. Add support for indenting arbitrary Algebra documents in 
>>>>    *Inspect.Opts*:
>>>>    defmodule Inspect.Opts do
>>>>    @t new_opt ::
>>>>    # ... |
>>>>    {:indent, String.indentation()}
>>>>    end
>>>>    
>>>> *Motivation:*
>>>>
>>>> I often want to indent code I provide as feedback to the user in 
>>>> exception messages in macros. I have to imagine other library authors, 
>>>> metaprogrammers, and LS developers would benefit as well.
>>>>
>>>> I often want to compose multi-line log messages with certain passages 
>>>> indented. I have to imagine other application developers have wanted the 
>>>> same.
>>>>
>>>> Please share if you can think of other ways you would use this 
>>>> functionality, or other stdlib APIs you can think of wanting indentation 
>>>> support for!
>>>>
>>>> *Rationale:*
>>>>
>>>> I find myself re-implementing this odd job often, not just for my 
>>>> stated use-cases here. The arguments for putting it in stlib are:
>>>>
>>>>    1. The String utility is general-purpose enough for many use-cases, 
>>>>    and so is potentially useful to many Elixir users.
>>>>    2. We can provide a better implementation than the naive *String.split 
>>>>    |> Kernel.<> |> Enum.join*.
>>>>    3. We can provide an implementation that works with Inspect.Algebra 
>>>>    documents and inspect.
>>>>    4. We can provide an option to *Macro.to_string* and 
>>>>    *Code.format_string!* where displaying multi-line strings is common.
>>>>
>>>> -- 
>>>> You received this message because you are subscribed to the Google 
>>>> Groups "elixir-lang-core" group.
>>>>
>>> To unsubscribe from this group and stop receiving emails from it, send 
>>>> an email to [email protected].
>>>>
>>>
>>>> To view this discussion visit 
>>>> https://groups.google.com/d/msgid/elixir-lang-core/f9b60654-29b2-4c06-80ea-6a6fa614f9d3n%40googlegroups.com
>>>>  
>>>> <https://groups.google.com/d/msgid/elixir-lang-core/f9b60654-29b2-4c06-80ea-6a6fa614f9d3n%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>> .
>>>>
>>> -- 
>>> You received this message because you are subscribed to the Google 
>>> Groups "elixir-lang-core" group.
>>>
>> To unsubscribe from this group and stop receiving emails from it, send an 
>>> email to [email protected].
>>> To view this discussion visit 
>>> https://groups.google.com/d/msgid/elixir-lang-core/mk4q396z.7bef578b-059a-4f47-9bbe-f8485fa3ecdc%40we.are.superhuman.com
>>>  
>>> <https://groups.google.com/d/msgid/elixir-lang-core/mk4q396z.7bef578b-059a-4f47-9bbe-f8485fa3ecdc%40we.are.superhuman.com?utm_medium=email&utm_source=footer>
>>> .
>>>
>>

-- 
You received this message because you are subscribed to the Google Groups 
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/elixir-lang-core/bfe3f1c7-a986-4bf0-8bde-ba99320f8e3cn%40googlegroups.com.

Reply via email to