context: using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Drawing;
  using System.IO;
  using System.Linq;
  using System.Text;
  using System.Text.RegularExpressions;
  using System.Threading.Tasks;
  using System.Windows.Forms;
  using GitCommands;
  using GitCommands.Patches;
  using GitCommands.Settings;
  using GitCommands.Utils;
  using GitExtUtils;
  using GitExtUtils.GitUI;
  using GitExtUtils.GitUI.Theming;
  using GitUI.CommandsDialogs;
  using GitUI.CommandsDialogs.SettingsDialog.Pages;
  using GitUI.Editor.Diff;
  using GitUI.Hotkey;
  using GitUI.Properties;
  using GitUI.UserControls;
  using GitUIPluginInterfaces;
  using JetBrains.Annotations;
  using ResourceManager;
  
  namespace GitUI.Editor
  {
      [DefaultEvent("SelectedLineChanged")]
      public partial class FileViewer : GitModuleControl
      {
          /// <summary>
          /// Raised when the Escape key is pressed (and only when no selection exists, as the default behaviour of escape is to clear the selection).
          /// </summary>
          public event Action EscapePressed;
  
          /// <summary>
          /// The type of information currently shown in the file viewer
          /// </summary>
          private enum ViewMode
          {
              // Plain text
              Text,
  
              // Diff or patch
              Diff,
  
              // Diffs that will not be affected by diff arguments like white space etc (limited options)
              FixedDiff,
  
              // range-diff output
              RangeDiff,
  
              // Image viewer
              Image
          }
  
          private readonly TranslationString _largeFileSizeWarning = new("This file is {0:N1} MB. Showing large files can be slow. Click to show anyway.");
          private readonly TranslationString _cannotViewImage = new("Cannot view image {0}");
  
          public event EventHandler<SelectedLineEventArgs> SelectedLineChanged;
          public event EventHandler HScrollPositionChanged;
          public event EventHandler VScrollPositionChanged;
          public event EventHandler BottomScrollReached;
          public event EventHandler TopScrollReached;
          public event EventHandler RequestDiffView;
          public new event EventHandler TextChanged;
          public event EventHandler TextLoaded;
          public event CancelEventHandler ContextMenuOpening;
          public event EventHandler<EventArgs> ExtraDiffArgumentsChanged;
          public event EventHandler<EventArgs> PatchApplied;
  
          private readonly AsyncLoader _async;
          private readonly IFullPathResolver _fullPathResolver;
          private ViewMode _viewMode;
          private Encoding _encoding;
          private Func<Task> _deferShowFunc;
          private readonly ContinuousScrollEventManager _continuousScrollEventManager;
          private FileStatusItem _viewItem;
  
          private static string[] _rangeDiffFullPrefixes = { "      ", "    ++", "    + ", "     +", "    --", "    - ", "     -", "    +-", "    -+", "    " };
          private static string[] _combinedDiffFullPrefixes = { "  ", "++", "+ ", " +", "--", "- ", " -" };
          private static string[] _normalDiffFullPrefixes = { " ", "+", "-" };
  
          private static string[] _rangeDiffPrefixes = { "    " };
          private static string[] _combinedDiffPrefixes = { "+", "-", " +", " -" };
          private static string[] _normalDiffPrefixes = { "+", "-" };
  
          public FileViewer()
          {
              TreatAllFilesAsText = false;
              ShowEntireFile = false;
              NumberOfContextLines = AppSettings.NumberOfContextLines;
              InitializeComponent();
              InitializeComplete();
  
              UICommandsSourceSet += OnUICommandsSourceSet;
  
              internalFileViewer.MouseEnter += (_, e) => OnMouseEnter(e);
              internalFileViewer.MouseLeave += (_, e) => OnMouseLeave(e);
              internalFileViewer.MouseMove += (_, e) => OnMouseMove(e);
              internalFileViewer.KeyUp += (_, e) => OnKeyUp(e);
              internalFileViewer.EscapePressed += () => EscapePressed?.Invoke();
  
              _continuousScrollEventManager = new ContinuousScrollEventManager();
              _continuousScrollEventManager.BottomScrollReached += _continuousScrollEventManager_BottomScrollReached;
              _continuousScrollEventManager.TopScrollReached += _continuousScrollEventManager_TopScrollReached;
  
              PictureBox.MouseWheel += PictureBox_MouseWheel;
              internalFileViewer.SetContinuousScrollManager(_continuousScrollEventManager);
  
              TextLoaded += (sender, args) => AllowLinePatching = SupportLinePatching;
              _async = new AsyncLoader();
              _async.LoadingError +=
                  (_, e) =>
                  {
                      if (!IsDisposed)
                      {
                          ResetView(ViewMode.Text, null);
                          internalFileViewer.SetText("Unsupported file: \n\n" + e.Exception.ToString(), openWithDifftool: null /* not applicable */);
                          TextLoaded?.Invoke(this, null);
                      }
                  };
  
              IgnoreWhitespace = AppSettings.IgnoreWhitespaceKind;
              OnIgnoreWhitespaceChanged();
  
              ignoreWhitespaceAtEol.Image = Images.WhitespaceIgnoreEol.AdaptLightness();
              ignoreWhitespaceAtEolToolStripMenuItem.Image = ignoreWhitespaceAtEol.Image;
  
              ignoreWhiteSpaces.Image = Images.WhitespaceIgnore.AdaptLightness();
              ignoreWhitespaceChangesToolStripMenuItem.Image = ignoreWhiteSpaces.Image;
  
              ignoreAllWhitespaces.Image = Images.WhitespaceIgnoreAll.AdaptLightness();
              ignoreAllWhitespaceChangesToolStripMenuItem.Image = ignoreAllWhitespaces.Image;
  
              ShowEntireFile = AppSettings.ShowEntireFile;
              showEntireFileButton.Checked = ShowEntireFile;
              showEntireFileToolStripMenuItem.Checked = ShowEntireFile;
              SetStateOfContextLinesButtons();
  
              automaticContinuousScrollToolStripMenuItem.Image = Images.UiScrollBar.AdaptLightness();
              automaticContinuousScrollToolStripMenuItem.Checked = AppSettings.AutomaticContinuousScroll;
  
              showNonPrintChars.Image = Images.ShowWhitespace.AdaptLightness();
              showNonprintableCharactersToolStripMenuItem.Image = showNonPrintChars.Image;
              showNonPrintChars.Checked = AppSettings.ShowNonPrintingChars;
              showNonprintableCharactersToolStripMenuItem.Checked = AppSettings.ShowNonPrintingChars;
              ToggleNonPrintingChars(AppSettings.ShowNonPrintingChars);
  
              ShowSyntaxHighlightingInDiff = AppSettings.ShowSyntaxHighlightingInDiff;
              showSyntaxHighlighting.Image = Resources.SyntaxHighlighting.AdaptLightness();
              showSyntaxHighlighting.Checked = ShowSyntaxHighlightingInDiff;
              automaticContinuousScrollToolStripMenuItem.Text = Strings.ContScrollToNextFileOnlyWithAlt;
  
              IsReadOnly = true;
  
              internalFileViewer.MouseMove += (_, e) =>
              {
                  if (IsDiffView(_viewMode) && !fileviewerToolbar.Visible)
                  {
                      fileviewerToolbar.Visible = true;
                      fileviewerToolbar.Location = new Point(Width - fileviewerToolbar.Width - 40, 0);
                      fileviewerToolbar.BringToFront();
                  }
              };
              internalFileViewer.MouseLeave += (_, e) =>
              {
                  if (GetChildAtPoint(PointToClient(MousePosition)) != fileviewerToolbar &&
                      fileviewerToolbar is not null)
                  {
                      fileviewerToolbar.Visible = false;
                  }
              };
              internalFileViewer.TextChanged += (sender, e) =>
              {
                  if (IsDiffView(_viewMode))
                  {
                      internalFileViewer.AddPatchHighlighting();
                  }
  
                  TextChanged?.Invoke(sender, e);
              };
              internalFileViewer.HScrollPositionChanged += (sender, e) => HScrollPositionChanged?.Invoke(sender, e);
              internalFileViewer.VScrollPositionChanged += (sender, e) => VScrollPositionChanged?.Invoke(sender, e);
              internalFileViewer.SelectedLineChanged += (sender, e) => SelectedLineChanged?.Invoke(sender, e);
              internalFileViewer.DoubleClick += (_, args) => RequestDiffView?.Invoke(this, EventArgs.Empty);
  
              HotkeysEnabled = true;
  
              if (!IsDesignModeActive && ContextMenuStrip is null)
              {
                  ContextMenuStrip = contextMenu;
              }
  
              contextMenu.Opening += (sender, e) =>
              {
                  copyToolStripMenuItem.Enabled = internalFileViewer.GetSelectionLength() > 0;
                  ContextMenuOpening?.Invoke(sender, e);
              };
  
              _fullPathResolver = new FullPathResolver(() => Module.WorkingDir);
              SupportLinePatching = false;
          }
  
          // Public properties
  
          [Browsable(false)]
          public byte[] FilePreamble { get; private set; }
  
          [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          [Browsable(false)]
          public new Font Font
          {
              get => internalFileViewer.Font;
              set => internalFileViewer.Font = value;
          }
  
          [DefaultValue(true)]
          [Category("Behavior")]
          public bool IsReadOnly
          {
              get => internalFileViewer.IsReadOnly;
              set => internalFileViewer.IsReadOnly = value;
          }
  
          [DefaultValue(null)]
          [Description("If true line numbers are shown in the textarea")]
          [Category("Appearance")]
          public bool? ShowLineNumbers
          {
              get => internalFileViewer.ShowLineNumbers;
              set => internalFileViewer.ShowLineNumbers = value;
          }
  
          [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          [Browsable(false)]
          public Encoding Encoding
          {
              get
              {
                  if (_encoding is null)
                  {
                      _encoding = Module.FilesEncoding;
                  }
  
                  return _encoding;
              }
              set
              {
                  _encoding = value;
  
                  this.InvokeAsync(() =>
                  {
                      if (_encoding is not null)
                      {
                          encodingToolStripComboBox.Text = _encoding.EncodingName;
                      }
                      else
                      {
                          encodingToolStripComboBox.SelectedIndex = -1;
                      }
                  }).FileAndForget();
              }
          }
  
          public void ScrollToTop()
          {
              internalFileViewer.ScrollToTop();
          }
  
          public void ScrollToBottom()
          {
              internalFileViewer.ScrollToBottom();
          }
  
          [DefaultValue(0)]
          [Browsable(false)]
          public int HScrollPosition
          {
              get => internalFileViewer.HScrollPosition;
              set => internalFileViewer.HScrollPosition = value;
          }
  
          [DefaultValue(0)]
          [Browsable(false)]
          public int VScrollPosition
          {
              get => internalFileViewer.VScrollPosition;
              set => internalFileViewer.VScrollPosition = value;
          }
  
          // Private properties
  
          [Description("Sets what kind of whitespace changes shall be ignored in diffs")]
          [DefaultValue(IgnoreWhitespaceKind.None)]
          private IgnoreWhitespaceKind IgnoreWhitespace { get; set; }
  
          [Description("Show diffs with <n> lines of context.")]
          [DefaultValue(3)]
          private int NumberOfContextLines { get; set; }
  
          [Description("Show diffs with entire file.")]
          [DefaultValue(false)]
          private bool ShowEntireFile { get; set; }
  
          [Description("Treat all files as text.")]
          [DefaultValue(false)]
          private bool TreatAllFilesAsText { get; set; }
  
          [Description("Show syntax highlighting in diffs.")]
          [DefaultValue(true)]
          private bool ShowSyntaxHighlightingInDiff { get; set; }
  
          // Public methods
  
          public void SetGitBlameGutter(IEnumerable<GitBlameEntry> gitBlameEntries)
          {
              internalFileViewer.ShowGutterAvatars = AppSettings.BlameShowAuthorAvatar;
  
              if (AppSettings.BlameShowAuthorAvatar)
              {
                  internalFileViewer.SetGitBlameGutter(gitBlameEntries);
              }
          }
  
          public void ReloadHotkeys()
          {
              Hotkeys = HotkeySettingsManager.LoadHotkeys(HotkeySettingsName);
              stageSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.StageLines);
              unstageSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.UnstageLines);
              resetSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.ResetLines);
          }
  
          public ToolStripSeparator AddContextMenuSeparator()
          {
              var separator = new ToolStripSeparator();
              contextMenu.Items.Add(separator);
              return separator;
          }
  
          public ToolStripMenuItem AddContextMenuEntry(string text, EventHandler toolStripItem_Click)
          {
              var toolStripItem = new ToolStripMenuItem(text);
              contextMenu.Items.Add(toolStripItem);
              toolStripItem.Click += toolStripItem_Click;
              return toolStripItem;
          }
  
          public void EnableScrollBars(bool enable)
          {
              internalFileViewer.EnableScrollBars(enable);
          }
  
          public ArgumentString GetExtraDiffArguments(bool isRangeDiff = false)
          {
              return new ArgumentBuilder
              {
                  { IgnoreWhitespace == IgnoreWhitespaceKind.AllSpace, "--ignore-all-space" },
                  { IgnoreWhitespace == IgnoreWhitespaceKind.Change, "--ignore-space-change" },
                  { IgnoreWhitespace == IgnoreWhitespaceKind.Eol, "--ignore-space-at-eol" },
                  { ShowEntireFile, "--inter-hunk-context=9000 --unified=9000", $"--unified={NumberOfContextLines}" },
  
                  // Handle zero context as showing no file changes, to get the summary only
                  { isRangeDiff && NumberOfContextLines == 0, "--no-patch " },
                  { TreatAllFilesAsText, "--text" }
              };
          }
  
          public string GetSelectedText()
          {
              return internalFileViewer.GetSelectedText();
          }
  
          public int GetSelectionPosition()
          {
              return internalFileViewer.GetSelectionPosition();
          }
  
          public int GetSelectionLength()
          {
              return internalFileViewer.GetSelectionLength();
          }
  
          public void GoToLine(int line)
          {
              internalFileViewer.GoToLine(line);
          }
  
          public int GetLineFromVisualPosY(int visualPosY)
          {
              return internalFileViewer.GetLineFromVisualPosY(visualPosY);
          }
  
          public void HighlightLines(int startLine, int endLine, Color color)
          {
              internalFileViewer.HighlightLines(startLine, endLine, color);
          }
  
          public void ClearHighlighting()
          {
              internalFileViewer.ClearHighlighting();
          }
  
          public string GetText() => internalFileViewer.GetText();
  
          /// <summary>
          /// Present the text as a patch in the file viewer
          /// </summary>
          /// <param name="item">The gitItem to present</param>
          /// <param name="text">The patch text</param>
          /// <param name="openWithDifftool">The action to open the difftool</param>
          public async Task ViewPatchAsync(FileStatusItem item, string text, Action openWithDifftool)
          {
              await ShowOrDeferAsync(
                 text.Length,
                 () =>
                 {
                     ResetView(ViewMode.Diff, item.Item.Name, item, text: text);
                     internalFileViewer.SetText(text, openWithDifftool, isDiff: IsDiffView(_viewMode), isRangeDiff: _viewMode == ViewMode.RangeDiff);
  
                     TextLoaded?.Invoke(this, null);
                     return Task.CompletedTask;
                 });
          }
  
          /// <summary>
          /// Present the text as a patch in the file viewer, for GitHub
          /// </summary>
          /// <param name="fileName">The fileName to present</param>
          /// <param name="text">The patch text</param>
          /// <param name="openWithDifftool">The action to open the difftool</param>
          public void ViewFixedPatch([CanBeNull] string fileName,
              [NotNull] string text,
              [CanBeNull] Action openWithDifftool = null)
          {
              ThreadHelper.JoinableTaskFactory.Run(
                  () => ViewPrivateAsync(fileName, text, openWithDifftool, ViewMode.FixedDiff));
          }
  
          public async Task ViewFixedPatchAsync(string fileName, string text, Action openWithDifftool = null)
              => await ViewPrivateAsync(fileName, text, openWithDifftool, ViewMode.FixedDiff);
  
          public async Task ViewRangeDiffAsync(string fileName, string text)
              => await ViewPrivateAsync(fileName, text, openWithDifftool: null, ViewMode.RangeDiff);
  
          public void ViewText([CanBeNull] string fileName,
              [NotNull] string text,
              [CanBeNull] Action openWithDifftool = null)
          {
              ThreadHelper.JoinableTaskFactory.Run(
                  () => ViewTextAsync(fileName, text, openWithDifftool));
          }
  
          public async Task ViewTextAsync([CanBeNull] string fileName, [NotNull] string text,
              [CanBeNull] Action openWithDifftool = null, bool checkGitAttributes = false)
          {
              await ShowOrDeferAsync(
                  text.Length,
                  () =>
                  {
                      ResetView(ViewMode.Text, fileName);
  
                      // Check for binary file. Using gitattributes could be misleading for a changed file,
                      // but not much other can be done
                      bool isBinary = (checkGitAttributes && FileHelper.IsBinaryFileName(Module, fileName))
                                      || FileHelper.IsBinaryFileAccordingToContent(text);
  
                      if (isBinary)
                      {
                          try
                          {
                              var summary = new StringBuilder()
                                  .AppendLine("Binary file:")
                                  .AppendLine()
                                  .AppendLine(fileName)
                                  .AppendLine()
                                  .AppendLine($"{text.Length:N0} bytes:")
                                  .AppendLine();
                              internalFileViewer.SetText(summary.ToString(), openWithDifftool);
  
                              ToHexDump(Encoding.ASCII.GetBytes(text), summary);
                              internalFileViewer.SetText(summary.ToString(), openWithDifftool);
                          }
                          catch
                          {
                              internalFileViewer.SetText($"Binary file: {fileName} (Detected)", openWithDifftool);
                          }
                      }
                      else
                      {
                          internalFileViewer.SetText(text, openWithDifftool);
                      }
  
                      TextLoaded?.Invoke(this, null);
                      return Task.CompletedTask;
                  });
          }
  
          public Task ViewGitItemRevisionAsync(GitItemStatus file, ObjectId revision, [CanBeNull] Action openWithDifftool = null)
          {
              if (revision == ObjectId.WorkTreeId)
              {
                  // No blob exists for worktree, present contents from file system
                  return ViewFileAsync(file.Name, file.IsSubmodule, openWithDifftool);
              }
  
              if (file.TreeGuid is null)
              {
                  file.TreeGuid = Module.GetFileBlobHash(file.Name, revision);
              }
  
              return ViewGitItemAsync(file, openWithDifftool);
          }
  
          /// <summary>
          /// View the git item with the TreeGuid
          /// </summary>
          /// <param name="file">GitItem file, with TreeGuid</param>
          /// <param name="openWithDifftool">difftool command</param>
          /// <returns>Task to view the item</returns>
          public Task ViewGitItemAsync(GitItemStatus file, [CanBeNull] Action openWithDifftool = null)
          {
              var sha = file.TreeGuid?.ToString();
              var isSubmodule = file.IsSubmodule;
  
              if (!isSubmodule && file.IsNew && file.Staged == StagedStatus.Index)
              {
                  // File system access for other than Worktree,
                  // to handle that git-status does not detect details for untracked (git-diff --no-index will not give info)
                  var fullPath = PathUtil.Combine(Module.WorkingDir, file.Name);
                  if (Directory.Exists(fullPath) && GitModule.IsValidGitWorkingDir(fullPath))
                  {
                      isSubmodule = true;
                  }
              }
  
              return ViewItemAsync(
                  file.Name,
                  isSubmodule,
                  getImage: GetImage,
                  getFileText: GetFileTextIfBlobExists,
                  getSubmoduleText: () => LocalizationHelpers.GetSubmoduleText(Module, file.Name.TrimEnd('/'), sha, cache: true),
                  openWithDifftool: openWithDifftool);
  
              string GetFileTextIfBlobExists()
              {
                  FilePreamble = new byte[] { };
                  return file.TreeGuid is not null ? Module.GetFileText(file.TreeGuid, Encoding) : string.Empty;
              }
  
              Image GetImage()
              {
                  try
                  {
                      using (var stream = Module.GetFileStream(sha))
                      {
                          return CreateImage(file.Name, stream);
                      }
                  }
                  catch
                  {
                      return null;
                  }
              }
          }
  
          /// <summary>
          /// Get contents in the file system async if not too big, otherwise ask user
          /// </summary>
          /// <param name="fileName">The file/submodule path</param>
          /// <param name="isSubmodule">If submodule</param>
          /// <param name="openWithDifftool">Diff action</param>
          /// <returns>Task</returns>
          public Task ViewFileAsync(string fileName, bool isSubmodule = false, [CanBeNull] Action openWithDifftool = null)
          {
              string fullPath = _fullPathResolver.Resolve(fileName);
  
              if (isSubmodule && !GitModule.IsValidGitWorkingDir(fullPath))
              {
                  return ViewTextAsync(fileName, "Invalid submodule: " + fileName);
              }
  
              if (!isSubmodule && (fileName.EndsWith("/") || Directory.Exists(fullPath)))
              {
                  if (!GitModule.IsValidGitWorkingDir(fullPath))
                  {
                      return ViewTextAsync(fileName, "Directory: " + fileName);
                  }
  
                  isSubmodule = true;
              }
  
              return ShowOrDeferAsync(
                  fileName,
                  () => ViewItemAsync(
                      fileName,
                      isSubmodule,
                      getImage: GetImage,
                      getFileText: GetFileText,
                      getSubmoduleText: () => LocalizationHelpers.GetSubmoduleText(Module, fileName.TrimEnd('/'), "", cache: false),
                      openWithDifftool));
  
              Image GetImage()
              {
                  try
                  {
                      var path = _fullPathResolver.Resolve(fileName);
  
                      if (!File.Exists(path))
                      {
                          return null;
                      }
  
                      using (var stream = File.OpenRead(path))
                      {
                          return CreateImage(fileName, stream);
                      }
                  }
                  catch
                  {
                      return null;
                  }
              }
  
              string GetFileText()
              {
                  var path = File.Exists(fileName)
                      ? fileName
                      : _fullPathResolver.Resolve(fileName);
  
                  if (!File.Exists(path))
                  {
                      return $"File {path} does not exist";
                  }
  
                  using (var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                  using (var reader = new StreamReader(stream, Module.FilesEncoding))
                  {
  #pragma warning disable VSTHRD103 // Call async methods when in an async method
                      var content = reader.ReadToEnd();
  #pragma warning restore VSTHRD103 // Call async methods when in an async method
                      FilePreamble = reader.CurrentEncoding.GetPreamble();
                      return content;
                  }
              }
          }
  
          public void Clear()
          {
              ThreadHelper.JoinableTaskFactory.Run(() => ViewTextAsync("", ""));
          }
  
          /// <summary>
          /// If the file viewer contents support line patches
          /// </summary>
          public bool SupportLinePatching { get; private set; }
  
          /// <summary>
          /// Configuration to require that the form using the viewer reloads contents before allowing next line patch
          /// by clearing <see cref="AllowLinePatching" />
          /// Used for index/worktree where line patches modifies the diff
          /// </summary>
          public bool LinePatchingBlocksUntilReload { private get; set; }
  
          /// <summary>
          /// Current state for line patching allowed for worktree/index
          /// Cleared when the file is reloaded
          /// </summary>
          private bool AllowLinePatching { get; set; }
  
          public void SetFileLoader(GetNextFileFnc fileLoader)
          {
              internalFileViewer.SetFileLoader(fileLoader);
          }
  
          public void CherryPickAllChanges()
          {
              ApplySelectedLines(allFile: true, reverse: false);
          }
  
          // Protected
  
          /// <summary>
          /// Clean up any resources being used.
          /// </summary>
          /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
          protected override void Dispose(bool disposing)
          {
              if (disposing)
              {
                  UICommandsSourceSet -= OnUICommandsSourceSet;
                  _async.Dispose();
                  components?.Dispose();
  
                  if (TryGetUICommands(out var uiCommands))
                  {
                      uiCommands.PostSettings -= UICommands_PostSettings;
                  }
              }
  
              base.Dispose(disposing);
          }
  
          protected override void DisposeUICommandsSource()
          {
              UICommandsSource.UICommandsChanged -= OnUICommandsChanged;
              base.DisposeUICommandsSource();
          }
  
          protected override void OnRuntimeLoad()
          {
              ReloadHotkeys();
              Font = AppSettings.FixedWidthFont;
  
              DetectDefaultEncoding();
              return;
  
              void DetectDefaultEncoding()
              {
                  var encodings = AppSettings.AvailableEncodings.Values.Select(e => e.EncodingName).ToArray();
                  encodingToolStripComboBox.Items.AddRange(encodings);
                  encodingToolStripComboBox.ResizeDropDownWidth(50, 250);
  
                  var defaultEncodingName = Encoding.Default.EncodingName;
  
                  for (int i = 0; i < encodings.Length; i++)
                  {
                      if (string.Equals(encodings[i], defaultEncodingName, StringComparison.OrdinalIgnoreCase))
                      {
                          encodingToolStripComboBox.Items[i] = "Default (" + Encoding.Default.HeaderName + ")";
                          break;
                      }
                  }
              }
          }
  
          // Private methods
  
          private static bool IsDiffView(ViewMode viewMode)
          {
              return viewMode is (ViewMode.Diff or ViewMode.FixedDiff or ViewMode.RangeDiff);
          }
  
          private async Task ViewPrivateAsync(string fileName, string text, Action openWithDifftool, ViewMode viewMode = ViewMode.Diff)
          {
              await ShowOrDeferAsync(
                  text.Length,
                  () =>
                  {
                      ResetView(viewMode, fileName, text: text);
                      internalFileViewer.SetText(text, openWithDifftool, isDiff: IsDiffView(_viewMode), isRangeDiff: _viewMode == ViewMode.RangeDiff);
  
                      TextLoaded?.Invoke(this, null);
                      return Task.CompletedTask;
                  });
          }
  
          private void CopyNotStartingWith(char startChar)
          {
              string code = internalFileViewer.GetSelectedText();
              bool noSelection = false;
  
              if (string.IsNullOrEmpty(code))
              {
                  code = internalFileViewer.GetText();
                  noSelection = true;
              }
  
              if (IsDiffView(_viewMode))
              {
                  // add artificial space if selected text is not starting from line beginning, it will be removed later
                  int pos = noSelection ? 0 : internalFileViewer.GetSelectionPosition();
                  string fileText = internalFileViewer.GetText();
  
                  if (pos > 0 && fileText[pos - 1] != '\n')
                  {
                      code = " " + code;
                  }
  
                  var lines = code.Split('\n')
                      .Where(s => s.Length == 0 || s[0] != startChar || (s.Length > 2 && s[1] == s[0] && s[2] == s[0]));
                  var hpos = fileText.IndexOf("\n@@");
  
                  // if header is selected then don't remove diff extra chars
                  if (hpos <= pos)
                  {
                      char[] specials = { ' ', '-', '+' };
                      lines = lines.Select(s => s.Length > 0 && specials.Any(c => c == s[0]) ? s.Substring(1) : s);
                  }
  
                  code = string.Join("\n", lines);
              }
  
              ClipboardUtil.TrySetText(code.AdjustLineEndings(Module.EffectiveConfigFile.core.autocrlf.Value));
          }
  
          private StagedStatus ViewItemStagedStatus()
          {
              var stagedStatus = _viewItem?.Item?.Staged ?? StagedStatus.Unknown;
              if (stagedStatus == StagedStatus.Unknown)
              {
                  stagedStatus = GitModule.GetStagedStatus(_viewItem?.FirstRevision?.ObjectId,
                      _viewItem?.SecondRevision?.ObjectId,
                      _viewItem?.SecondRevision?.FirstParentId);
              }
  
              return stagedStatus;
          }
  
          private bool ViewItemIsWorkTree()
          {
              return _viewMode == ViewMode.Diff
                  && ViewItemStagedStatus() == StagedStatus.WorkTree
                  && SupportLinePatching;
          }
  
          private bool ViewItemIsIndex()
          {
              return _viewMode == ViewMode.Diff
                  && ViewItemStagedStatus() == StagedStatus.Index
                  && SupportLinePatching;
          }
  
          private void SetVisibilityDiffContextMenu(ViewMode viewMode)
          {
              // stage and reset has different implementation depending on the viewItem
              // For the user it looks the same and they expect the same menu item (and hotkey)
              var isIndex = ViewItemIsIndex();
              stageSelectedLinesToolStripMenuItem.Visible = SupportLinePatching && !isIndex;
              unstageSelectedLinesToolStripMenuItem.Visible = isIndex;
              resetSelectedLinesToolStripMenuItem.Visible = SupportLinePatching;
  
              // RangeDiff patch is undefined, could be new/old commit or to parents
              copyPatchToolStripMenuItem.Visible = viewMode is (ViewMode.Diff or ViewMode.FixedDiff);
              copyNewVersionToolStripMenuItem.Visible = viewMode is (ViewMode.Diff or ViewMode.FixedDiff);
              copyOldVersionToolStripMenuItem.Visible = viewMode is (ViewMode.Diff or ViewMode.FixedDiff);
  
              ignoreWhitespaceAtEolToolStripMenuItem.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
              ignoreWhitespaceChangesToolStripMenuItem.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
              ignoreAllWhitespaceChangesToolStripMenuItem.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
              increaseNumberOfLinesToolStripMenuItem.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
              decreaseNumberOfLinesToolStripMenuItem.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
              showEntireFileToolStripMenuItem.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
              toolStripSeparator2.Visible = IsDiffView(viewMode);
              treatAllFilesAsTextToolStripMenuItem.Visible = IsDiffView(viewMode);
  
              // toolbar
              nextChangeButton.Visible = IsDiffView(viewMode);
              previousChangeButton.Visible = IsDiffView(viewMode);
              increaseNumberOfLines.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
              decreaseNumberOfLines.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
              showEntireFileButton.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
              showSyntaxHighlighting.Visible = IsDiffView(viewMode);
              ignoreWhitespaceAtEol.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
              ignoreWhiteSpaces.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
              ignoreAllWhitespaces.Visible = viewMode is (ViewMode.Diff or ViewMode.RangeDiff);
          }
  
          private void OnExtraDiffArgumentsChanged()
          {
              ExtraDiffArgumentsChanged?.Invoke(this, EventArgs.Empty);
          }
  
          private Task ShowOrDeferAsync(string fileName, Func<Task> showFunc)
          {
              return ShowOrDeferAsync(GetFileLength(), showFunc);
  
              long GetFileLength()
              {
                  try
                  {
                      var resolvedPath = _fullPathResolver.Resolve(fileName);
  
                      if (File.Exists(resolvedPath))
                      {
                          var file = new FileInfo(resolvedPath);
                          return file.Length;
                      }
                  }
                  catch (Exception ex)
                  {
                      MessageBox.Show(this, $"{ex.Message}{Environment.NewLine}{fileName}", Strings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
                  }
  
                  // If the file does not exist, it doesn't matter what size we
                  // return as nothing will be shown anyway.
                  return 0;
              }
          }
  
          private Task ShowOrDeferAsync(long contentLength, Func<Task> showFunc)
          {
              const long maxLength = 5 * 1024 * 1024;
  
              if (contentLength > maxLength)
              {
                  Clear();
                  Refresh();
                  _NO_TRANSLATE_lblShowPreview.Text = string.Format(_largeFileSizeWarning.Text, contentLength / (1024d * 1024));
                  _NO_TRANSLATE_lblShowPreview.Show();
                  _deferShowFunc = showFunc;
                  return Task.CompletedTask;
              }
              else
              {
                  _NO_TRANSLATE_lblShowPreview.Hide();
                  _deferShowFunc = null;
                  return showFunc();
              }
          }
  
          [NotNull]
          private static Image CreateImage([NotNull] string fileName, [NotNull] Stream stream)
          {
              if (IsIcon())
              {
                  using (var icon = new Icon(stream))
                  {
                      return icon.ToBitmap();
                  }
              }
  
              return new Bitmap(CopyStream());
  
              bool IsIcon()
              {
                  return fileName.EndsWith(".ico", StringComparison.CurrentCultureIgnoreCase);
              }
  
              MemoryStream CopyStream()
              {
                  var copy = new MemoryStream();
                  stream.CopyTo(copy);
                  return copy;
              }
          }
  
          private void OnIgnoreWhitespaceChanged()
          {
              switch (IgnoreWhitespace)
              {
                  case IgnoreWhitespaceKind.None:
                      ignoreWhitespaceAtEol.Checked = false;
                      ignoreWhitespaceAtEolToolStripMenuItem.Checked = ignoreWhitespaceAtEol.Checked;
                      ignoreWhiteSpaces.Checked = false;
                      ignoreWhitespaceChangesToolStripMenuItem.Checked = ignoreWhiteSpaces.Checked;
                      ignoreAllWhitespaces.Checked = false;
                      ignoreAllWhitespaceChangesToolStripMenuItem.Checked = ignoreAllWhitespaces.Checked;
                      break;
                  case IgnoreWhitespaceKind.Eol:
                      ignoreWhitespaceAtEol.Checked = true;
                      ignoreWhitespaceAtEolToolStripMenuItem.Checked = ignoreWhitespaceAtEol.Checked;
                      ignoreWhiteSpaces.Checked = false;
                      ignoreWhitespaceChangesToolStripMenuItem.Checked = ignoreWhiteSpaces.Checked;
                      ignoreAllWhitespaces.Checked = false;
                      ignoreAllWhitespaceChangesToolStripMenuItem.Checked = ignoreAllWhitespaces.Checked;
                      break;
                  case IgnoreWhitespaceKind.Change:
                      ignoreWhitespaceAtEol.Checked = true;
                      ignoreWhitespaceAtEolToolStripMenuItem.Checked = ignoreWhitespaceAtEol.Checked;
                      ignoreWhiteSpaces.Checked = true;
                      ignoreWhitespaceChangesToolStripMenuItem.Checked = ignoreWhiteSpaces.Checked;
                      ignoreAllWhitespaces.Checked = false;
                      ignoreAllWhitespaceChangesToolStripMenuItem.Checked = ignoreAllWhitespaces.Checked;
                      break;
                  case IgnoreWhitespaceKind.AllSpace:
                      ignoreWhitespaceAtEol.Checked = true;
                      ignoreWhitespaceAtEolToolStripMenuItem.Checked = ignoreWhitespaceAtEol.Checked;
                      ignoreWhiteSpaces.Checked = true;
                      ignoreWhitespaceChangesToolStripMenuItem.Checked = ignoreWhiteSpaces.Checked;
                      ignoreAllWhitespaces.Checked = true;
                      ignoreAllWhitespaceChangesToolStripMenuItem.Checked = ignoreAllWhitespaces.Checked;
                      break;
                  default:
                      throw new NotSupportedException("Unsupported value for IgnoreWhitespaceKind: " + IgnoreWhitespace);
              }
  
              AppSettings.IgnoreWhitespaceKind = IgnoreWhitespace;
          }
  
          private void ResetView(ViewMode viewMode, [CanBeNull] string fileName, [CanBeNull] FileStatusItem item = null, string text = null)
          {
              _viewMode = viewMode;
              _viewItem = item;
              if (_viewMode == ViewMode.Text
                  && !string.IsNullOrEmpty(fileName)
                  && (fileName.EndsWith(".diff", StringComparison.OrdinalIgnoreCase)
                      || fileName.EndsWith(".patch", StringComparison.OrdinalIgnoreCase)))
              {
                  _viewMode = ViewMode.FixedDiff;
              }
  
              SupportLinePatching = IsDiffView(_viewMode)
                      && !Module.IsBareRepository()
                      && File.Exists(_fullPathResolver.Resolve(fileName))
                      && (text?.Contains("@@") ?? false);
  
              SetVisibilityDiffContextMenu(_viewMode);
              ClearImage();
              PictureBox.Visible = _viewMode == ViewMode.Image;
  
gt:             internalFileViewer.Visible = _viewMode != ViewMode.Image;