Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improvement: better UX around large files #222

Merged
merged 1 commit into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
210 changes: 151 additions & 59 deletions lib/igniter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,11 @@ defmodule Igniter do
if opts[:dry_run] || !opts[:yes] do
Mix.shell().info("\n#{IO.ANSI.green()}#{title}#{IO.ANSI.reset()}:")

display_diff(Rewrite.sources(igniter.rewrite), opts)
if !opts[:yes] && too_long_to_display?(igniter) do
handle_long_diff(igniter, opts)
else
display_diff(Rewrite.sources(igniter.rewrite), opts)
end
end

:dry_run_with_changes
Expand Down Expand Up @@ -1174,6 +1178,90 @@ defmodule Igniter do
end
end

@line_limit 1000

defp too_long_to_display?(igniter) do
if igniter.assigns[:test_mode?] do
false
else
Enum.reduce_while(igniter.rewrite, {0, false}, fn source, {count, res} ->
count = count + Enum.count(String.split(source_diff(source, false), "\n"))

if count > @line_limit do
{:halt, {count, true}}
else
{:cont, {count, res}}
end
end)
|> elem(1)
end
end

defp handle_long_diff(igniter, opts) do
files_changed =
igniter.rewrite
|> Enum.filter(&changed?/1)
|> Enum.group_by(&Rewrite.Source.from?(&1, :string))
|> Enum.sort_by(&elem(&1, 0))
|> Enum.map_join("\n\n", fn
{true, sources} ->
"Creating: \n\n" <>
Enum.map_join(sources, "\n", &" * #{Rewrite.Source.get(&1, :path)}")

{false, sources} ->
"Updating: \n\n" <>
Enum.map_join(sources, "\n", &" * #{Rewrite.Source.get(&1, :path)}")
end)

files_changed =
if Enum.empty?(igniter.moves) do
files_changed
else
("Moving: \n\n" <>
igniter.moves)
|> Enum.sort_by(&elem(&1, 0))
|> Enum.map(fn {from, to} ->
"#{IO.ANSI.red()} #{from}#{IO.ANSI.reset()}: #{IO.ANSI.green()}#{to}#{IO.ANSI.reset()}"
end)
end

options = [
write: "Proceed *without* viewing changes. (default)",
display: "Display the diff inline anyway.",
patch_file:
"Write to `.igniter` so you can preview all of the changes, and wait to proceed."
]

Igniter.Util.IO.select(
"Too many changes to automatically display a full diff (>= #{@line_limit} lines changed).\n" <>
"The following files will be changed:\n\n" <>
files_changed <> "\n\nHow would you like to proceed?",
options,
display: &elem(&1, 1),
default: :write
)
|> elem(0)
|> case do
:display ->
display_diff(Rewrite.sources(igniter.rewrite), opts)

:patch_file ->
File.write!(
".igniter",
diff(Rewrite.sources(igniter.rewrite), Keyword.put(opts, :color?, false))
)

Mix.shell().info(
"Diff:\n\n#{IO.ANSI.yellow()}View the diff by opening `#{Path.expand(".igniter")}`.#{IO.ANSI.reset()}"
)

:write ->
Mix.shell().info("#{IO.ANSI.yellow()}Not shown due to line count.#{IO.ANSI.reset()}")

:ok
end
end

defp display_diff(sources, opts) do
if !opts[:yes] do
Mix.shell().info(diff(sources))
Expand All @@ -1191,74 +1279,78 @@ defmodule Igniter do
source -> source
end

cond do
Rewrite.Source.from?(source, :string) &&
String.valid?(Rewrite.Source.get(source, :content)) ->
content_lines =
source
|> Rewrite.Source.get(:content)
|> String.split("\n")

space_padding =
content_lines
|> length()
|> to_string()
|> String.length()

diffish_looking_text =
content_lines
|> Enum.with_index(1)
|> Enum.map_join(fn {line, line_number} ->
IO.ANSI.format(
[
String.pad_trailing(to_string(line_number), space_padding),
" ",
:yellow,
"|",
:green,
line,
"\n"
],
color?
)
end)
source_diff(source, color?)
end)
end

if String.trim(diffish_looking_text) != "" do
"""
defp source_diff(source, color?) do
cond do
Rewrite.Source.from?(source, :string) &&
String.valid?(Rewrite.Source.get(source, :content)) ->
content_lines =
source
|> Rewrite.Source.get(:content)
|> String.split("\n")

space_padding =
content_lines
|> length()
|> to_string()
|> String.length()

diffish_looking_text =
content_lines
|> Enum.with_index(1)
|> Enum.map_join(fn {line, line_number} ->
IO.ANSI.format(
[
String.pad_trailing(to_string(line_number), space_padding),
" ",
:yellow,
"|",
:green,
line,
"\n"
],
color?
)
end)

Create: #{Rewrite.Source.get(source, :path)}
if String.trim(diffish_looking_text) != "" do
"""

#{diffish_looking_text}
"""
else
""
end
Create: #{Rewrite.Source.get(source, :path)}

String.valid?(Rewrite.Source.get(source, :content)) ->
diff = Rewrite.Source.diff(source, color: color?) |> IO.iodata_to_binary()
#{diffish_looking_text}
"""
else
""
end

if String.trim(diff) != "" do
"""
String.valid?(Rewrite.Source.get(source, :content)) ->
diff = Rewrite.Source.diff(source, color: color?) |> IO.iodata_to_binary()

Update: #{Rewrite.Source.get(source, :path)}
if String.trim(diff) != "" do
"""

#{diff}
"""
else
""
end
Update: #{Rewrite.Source.get(source, :path)}

!String.valid?(Rewrite.Source.get(source, :content)) ->
#{diff}
"""
Create: #{Rewrite.Source.get(source, :path)}
else
""
end

(content diff can't be displayed)
"""
!String.valid?(Rewrite.Source.get(source, :content)) ->
"""
Create: #{Rewrite.Source.get(source, :path)}

:else ->
""
end
end)
(content diff can't be displayed)
"""

true ->
""
end
end

@doc false
Expand Down Expand Up @@ -1642,7 +1734,7 @@ defmodule Igniter do
igniter.moves
|> Enum.sort_by(&elem(&1, 0))
|> Enum.map(fn {from, to} ->
[:red, Path.relative_to_cwd(from), :reset, ": ", :green, to]
[:red, from, :reset, ": ", :green, to]
end)
|> display_list("These files will be moved:")
end
Expand Down
14 changes: 12 additions & 2 deletions lib/igniter/util/io.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,22 @@ defmodule Igniter.Util.IO do
items
|> Enum.with_index()
|> Enum.map_join("\n", fn {item, index} ->
"#{index}. #{display.(item)}"
if Keyword.has_key?(opts, :default) && item == opts[:default] do
"#{IO.ANSI.green()}#{index}.#{IO.ANSI.reset()} #{display.(item)} (Default)"
else
"#{index}. #{display.(item)}"
end
end)

case String.trim(Mix.shell().prompt(prompt <> "\n" <> item_numbers <> "\nInput number ❯ ")) do
"" ->
select(prompt, items, opts)
case Keyword.fetch(opts, :default) do
{:ok, value} ->
value

:error ->
select(prompt, items, opts)
end

item ->
case Integer.parse(item) do
Expand Down