diff --git a/docs/Config.md b/docs/Config.md index d85637429..f9c6a729b 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -29,268 +29,503 @@ to the top of your config file or via [Visual Studio Code settings.json config][ ## Default + ```yaml +# Config relating to the Lazygit UI gui: - # stuff relating to the UI - windowSize: 'normal' # one of 'normal' | 'half' | 'full' default is 'normal' - scrollHeight: 2 # how many lines you scroll by - scrollPastBottom: true # enable scrolling past the bottom - scrollOffMargin: 2 # how many lines to keep before/after the cursor when it reaches the top/bottom of the view; see 'Scroll-off Margin' section below - scrollOffBehavior: 'margin' # one of 'margin' | 'jump'; see 'Scroll-off Margin' section below - sidePanelWidth: 0.3333 # number from 0 to 1 - expandFocusedSidePanel: false - mainPanelSplitMode: 'flexible' # one of 'horizontal' | 'flexible' | 'vertical' - enlargedSideViewLocation: 'left' # one of 'left' | 'top' - language: 'auto' # one of 'auto' | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru' - timeFormat: '02 Jan 06' # https://pkg.go.dev/time#Time.Format - shortTimeFormat: '3:04PM' + # The number of lines you scroll by when scrolling the main window + scrollHeight: 2 + + # If true, allow scrolling past the bottom of the content in the main window + scrollPastBottom: true + + # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#scroll-off-margin + scrollOffMargin: 2 + + # One of: 'margin' (default) | 'jump' + scrollOffBehavior: margin + + # If true, capture mouse events. + # When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS. + mouseEvents: true + + # Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section. + # Number from 0 to 1.0. + sidePanelWidth: 0.3333 + + # Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split. + # Options are: + # - 'horizontal': split the window horizontally + # - 'vertical': split the window vertically + # - 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically + mainPanelSplitMode: flexible + + # How the window is split when in half screen mode (i.e. after hitting '+' once). + # Possible values: + # - 'left': split the window horizontally (side panel on the left, main view on the right) + # - 'top': split the window vertically (side panel on top, main view below) + enlargedSideViewLocation: left + + # One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru' + language: auto + + # Format used when displaying time e.g. commit time. + # Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format + timeFormat: 02 Jan 06 + + # Format used when displaying time if the time is less than 24 hours ago. + # Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format + shortTimeFormat: 3:04PM + + # Config relating to colors and styles. + # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes theme: + # Border color of focused window activeBorderColor: - green - bold + + # Border color of non-focused windows inactiveBorderColor: - - white + - default + + # Border color of focused window when searching in that window searchingActiveBorderColor: - cyan - bold + + # Color of keybindings help text in the bottom line optionsTextColor: - blue + + # Background color of selected line. + # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line selectedLineBgColor: - - blue # set to `default` to have no background colour - cherryPickedCommitBgColor: - - cyan + - blue + + # Foreground color of copied commit cherryPickedCommitFgColor: - blue + + # Background color of copied commit + cherryPickedCommitBgColor: + - cyan + + # Foreground color of marked base commit (for rebase) + markedBaseCommitFgColor: + - blue + + # Background color of marked base commit (for rebase) + markedBaseCommitBgColor: + - yellow + + # Color for file with unstaged changes unstagedChangesColor: - red + + # Default text color defaultFgColor: - default + + # Config relating to the commit length indicator commitLength: + # If true, show an indicator of commit message length show: true - mouseEvents: true - skipDiscardChangeWarning: false - skipStashWarning: false - showFileTree: true # for rendering changes files in a tree format - showListFooter: true # for seeing the '5 of 20' message in list panels + + # If true, show the '5 of 20' footer at the bottom of list views + showListFooter: true + + # If true, display the files in the file views as a tree. If false, display the files as a flat list. + # This can be toggled from within Lazygit with the '~' key, but that will not change the default. + showFileTree: true + + # If true, show a random tip in the command log when Lazygit starts showRandomTip: true - showBranchCommitHash: false # show commit hashes alongside branch names - showBottomLine: true # for hiding the bottom information line (unless it has important information to tell you) - showPanelJumps: true # for showing the jump-to-panel keybindings as panel subtitles + + # If true, show the command log showCommandLog: true - showIcons: false # deprecated: use nerdFontsVersion instead - nerdFontsVersion: "" # nerd fonts version to use ("2" or "3"); empty means don't show nerd font icons - showFileIcons: true # for hiding file icons in the file views - commitHashLength: 8 # length of commit hash in commits view. 0 shows '*' if NF icons aren't enabled + + # If true, show the bottom line that contains keybinding info and useful buttons. If false, this line will be hidden except to display a loader for an in-progress action. + showBottomLine: true + + # If true, show jump-to-window keybindings in window titles. + showPanelJumps: true + + # Nerd fonts version to use. + # One of: '2' | '3' | empty string (default) + # If empty, do not show icons. + nerdFontsVersion: "" + + # If true (default), file icons are shown in the file views. Only relevant if NerdFontsVersion is not empty. + showFileIcons: true + + # Length of commit hash in commits view. 0 shows '*' if NF icons aren't on. + commitHashLength: 8 + + # Height of the command log view commandLogSize: 8 - splitDiff: 'auto' # one of 'auto' | 'always' - skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor - border: 'rounded' # one of 'single' | 'double' | 'rounded' | 'hidden' - animateExplosion: true # shows an explosion animation when nuking the working tree - portraitMode: 'auto' # one of 'auto' | 'never' | 'always' - filterMode: 'substring' # one of 'substring' | 'fuzzy'; see 'Filtering' section below + + # Whether to split the main window when viewing file changes. + # One of: 'auto' | 'always' + # If 'auto', only split the main window when a file has both staged and unstaged changes + splitDiff: auto + + # Default size for focused window. Window size can be changed from within Lazygit with '+' and '_' (but this won't change the default). + # One of: 'normal' (default) | 'half' | 'full' + windowSize: normal + + # Window border style. + # One of 'rounded' (default) | 'single' | 'double' | 'hidden' + border: rounded + + # If true, show a seriously epic explosion animation when nuking the working tree. + animateExplosion: true + + # Whether to stack UI components on top of each other. + # One of 'auto' (default) | 'always' | 'never' + portraitMode: auto + + # How things are filtered when typing '/'. + # One of 'substring' (default) | 'fuzzy' + filterMode: substring + + # Config relating to the spinner. spinner: - frames: ['|', '/', '-', '\\'] - rate: 50 # spinner rate in milliseconds - statusPanelView: 'dashboard' # one of 'dashboard' | 'allBranchesLog' + # The frames of the spinner animation. + frames: + - '|' + - / + - '-' + - \ + + # The "speed" of the spinner in milliseconds. + rate: 50 + + # Status panel view. + # One of 'dashboard' (default) | 'allBranchesLog' + statusPanelView: dashboard + +# Config relating to git git: + # See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md paging: + # Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never' colorArg: always - useConfig: false + + # e.g. + # diff-so-fancy + # delta --dark --paging=never + # ydiff -p cat -s --wrap --width={{columnWidth}} + pager: "" + + # e.g. 'difft --color=always' + externalDiffCommand: "" + + # Config relating to committing commit: - signOff: false - autoWrapCommitMessage: true # automatic WYSIWYG wrapping of the commit message as you type - autoWrapWidth: 72 # if autoWrapCommitMessage is true, the width to wrap to + # Automatic WYSIWYG wrapping of the commit message as you type + autoWrapCommitMessage: true + + # If autoWrapCommitMessage is true, the width to wrap to + autoWrapWidth: 72 + + # Config relating to merging merging: - # only applicable to unix users - manualCommit: false - # extra args passed to `git merge`, e.g. --no-ff - args: '' + # Extra args passed to `git merge`, e.g. --no-ff + args: "" + + # list of branches that are considered 'main' branches, used when displaying commits + mainBranches: + - master + - main + + # Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP' + skipHookPrefix: WIP + + # If true, periodically fetch from remote + autoFetch: true + + # If true, periodically refresh files and submodules + autoRefresh: true + + # If true, pass the --all arg to git fetch + fetchAll: true + + # Command used when displaying the current branch git log in the main window + branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} -- + + # Command used to display git log of all branches in the main window + allBranchesLogCmd: git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium + + # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix + commitPrefix: + # pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*" + pattern: "" + + # Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] " + replace: "" + + # Config for showing the log in the commits view log: - # one of date-order, author-date-order, topo-order or default. - # topo-order makes it easier to read the git log graph, but commits may not - # appear chronologically. See https://git-scm.com/docs/git-log#_commit_ordering + # One of: 'date-order' | 'author-date-order' | 'topo-order' | 'default' + # 'topo-order' makes it easier to read the git log graph, but commits may not + # appear chronologically. See https://git-scm.com/docs/ # # Deprecated: Configure this with `Log menu -> Commit sort order` ( in the commits window by default). - order: 'topo-order' - # one of always, never, when-maximised - # this determines whether the git graph is rendered in the commits panel + order: topo-order + + # This determines whether the git graph is rendered in the commits panel + # One of 'always' | 'never' | 'when-maximised' # # Deprecated: Configure this with `Log menu -> Show git graph` ( in the commits window by default). - showGraph: 'always' - # displays the whole git graph by default in the commits panel (equivalent to passing the `--all` argument to `git log`) - showWholeGraph: false - skipHookPrefix: WIP - # The main branches. We colour commits green if they belong to one of these branches, - # so that you can easily see which commits are unique to your branch (coloured in yellow) - mainBranches: [master, main] - autoFetch: true - autoRefresh: true - fetchAll: true # Pass --all flag when running git fetch. Set to false to fetch only origin (or the current branch's upstream remote if there is one) - branchLogCmd: 'git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --' - allBranchesLogCmd: 'git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium' - overrideGpg: false # prevents lazygit from spawning a separate process when using GPG - disableForcePushing: false - parseEmoji: false - truncateCopiedCommitHashesTo: 12 # When copying commit hashes to the clipboard, truncate them to this length. Set to 40 to disable truncation. -os: - copyToClipboardCmd: '' # See 'Custom Command for Copying to Clipboard' section - editPreset: '' # see 'Configuring File Editing' section - edit: '' - editAtLine: '' - editAtLineAndWait: '' - open: '' - openLink: '' -refresher: - refreshInterval: 10 # File/submodule refresh interval in seconds. Auto-refresh can be disabled via option 'git.autoRefresh'. - fetchInterval: 60 # Re-fetch interval in seconds. Auto-fetch can be disabled via option 'git.autoFetch'. + showGraph: always + + # When copying commit hashes to the clipboard, truncate them to this + # length. Set to 40 to disable truncation. + truncateCopiedCommitHashesTo: 12 + +# Periodic update checks update: - method: prompt # can be: prompt | background | never - days: 14 # how often an update is checked for -confirmOnQuit: false -# determines whether hitting 'esc' will quit the application when there is nothing to cancel/close -quitOnTopLevelReturn: false -disableStartupPopups: false -notARepository: 'prompt' # one of: 'prompt' | 'create' | 'skip' | 'quit' -promptToReturnFromSubprocess: true # display confirmation when subprocess terminates + # One of: 'prompt' (default) | 'background' | 'never' + method: prompt + + # Period in days between update checks + days: 14 + +# Background refreshes +refresher: + # File/submodule refresh interval in seconds. + # Auto-refresh can be disabled via option 'git.autoRefresh'. + refreshInterval: 10 + + # Re-fetch interval in seconds. + # Auto-fetch can be disabled via option 'git.autoFetch'. + fetchInterval: 60 + +# Keybindings keybinding: universal: - quit: 'q' - quit-alt1: '' # alternative/alias of quit - return: '' # return to previous menu, will quit if there's nowhere to return - quitWithoutChangingDirectory: 'Q' - togglePanel: '' # goto the next panel - prevItem: '' # go one line up - nextItem: '' # go one line down - prevItem-alt: 'k' # go one line up - nextItem-alt: 'j' # go one line down - prevPage: ',' # go to next page in list - nextPage: '.' # go to previous page in list - gotoTop: '<' # go to top of list - gotoBottom: '>' # go to bottom of list - scrollLeft: 'H' # scroll left within list view - scrollRight: 'L' # scroll right within list view - prevBlock: '' # goto the previous block / panel - nextBlock: '' # goto the next block / panel - prevBlock-alt: 'h' # goto the previous block / panel - nextBlock-alt: 'l' # goto the next block / panel - jumpToBlock: ['1', '2', '3', '4', '5'] # goto the Nth block / panel - nextMatch: 'n' - prevMatch: 'N' - optionMenu: # show help menu - optionMenu-alt1: '?' # show help menu - select: '' - goInto: '' - openRecentRepos: '' - confirm: '' - remove: 'd' - new: 'n' - edit: 'e' - openFile: 'o' - scrollUpMain: '' # main panel scroll up - scrollDownMain: '' # main panel scroll down - scrollUpMain-alt1: 'K' # main panel scroll up - scrollDownMain-alt1: 'J' # main panel scroll down - scrollUpMain-alt2: '' # main panel scroll up - scrollDownMain-alt2: '' # main panel scroll down + quit: q + quit-alt1: + return: + quitWithoutChangingDirectory: Q + togglePanel: + prevItem: + nextItem: + prevItem-alt: k + nextItem-alt: j + prevPage: ',' + nextPage: . + scrollLeft: H + scrollRight: L + gotoTop: < + gotoBottom: '>' + toggleRangeSelect: v + rangeSelectDown: + rangeSelectUp: + prevBlock: + nextBlock: + prevBlock-alt: h + nextBlock-alt: l + nextBlock-alt2: + prevBlock-alt2: + jumpToBlock: + - "1" + - "2" + - "3" + - "4" + - "5" + nextMatch: "n" + prevMatch: "N" + startSearch: / + optionMenu: + optionMenu-alt1: '?' + select: + goInto: + confirm: + confirmInEditor: + remove: d + new: "n" + edit: e + openFile: o + scrollUpMain: + scrollDownMain: + scrollUpMain-alt1: K + scrollDownMain-alt1: J + scrollUpMain-alt2: + scrollDownMain-alt2: executeCustomCommand: ':' - createRebaseOptionsMenu: 'm' - pushFiles: 'P' - pullFiles: 'p' - refresh: 'R' - createPatchOptionsMenu: '' + createRebaseOptionsMenu: m + + # 'Files' appended for legacy reasons + pushFiles: P + + # 'Files' appended for legacy reasons + pullFiles: p + refresh: R + createPatchOptionsMenu: nextTab: ']' prevTab: '[' - nextScreenMode: '+' - prevScreenMode: '_' - undo: 'z' - redo: '' - filteringMenu: '' - diffingMenu: 'W' - diffingMenu-alt: '' # deprecated - copyToClipboard: '' - submitEditorText: '' + nextScreenMode: + + prevScreenMode: _ + undo: z + redo: + filteringMenu: + diffingMenu: W + diffingMenu-alt: + copyToClipboard: + openRecentRepos: + submitEditorText: extrasMenu: '@' - toggleWhitespaceInDiffView: '' + toggleWhitespaceInDiffView: increaseContextInDiffView: '}' decreaseContextInDiffView: '{' - toggleRangeSelect: 'v' - rangeSelectUp: '' - rangeSelectDown: '' + openDiffTool: status: - checkForUpdate: 'u' - recentRepos: '' + checkForUpdate: u + recentRepos: + allBranchesLogGraph: a files: - commitChanges: 'c' - commitChangesWithoutHook: 'w' # commit changes without pre-commit hook - amendLastCommit: 'A' - commitChangesWithEditor: 'C' - findBaseCommitForFixup: '' - confirmDiscard: 'x' - ignoreFile: 'i' - refreshFiles: 'r' - stashAllChanges: 's' - viewStashOptions: 'S' - toggleStagedAll: 'a' # stage/unstage all - viewResetOptions: 'D' - fetch: 'f' + commitChanges: c + commitChangesWithoutHook: w + amendLastCommit: A + commitChangesWithEditor: C + findBaseCommitForFixup: + confirmDiscard: x + ignoreFile: i + refreshFiles: r + stashAllChanges: s + viewStashOptions: S + toggleStagedAll: a + viewResetOptions: D + fetch: f toggleTreeView: '`' - openMergeTool: 'M' - openStatusFilter: '' + openMergeTool: M + openStatusFilter: + copyFileInfoToClipboard: "y" branches: - createPullRequest: 'o' - viewPullRequestOptions: 'O' - checkoutBranchByName: 'c' - forceCheckoutBranch: 'F' - rebaseBranch: 'r' - renameBranch: 'R' - mergeIntoCurrentBranch: 'M' - viewGitFlowOptions: 'i' - fastForward: 'f' # fast-forward this branch from its upstream - createTag: 'T' - pushTag: 'P' - setUpstream: 'u' # set as upstream of checked-out branch - fetchRemote: 'f' + createPullRequest: o + viewPullRequestOptions: O + copyPullRequestURL: + checkoutBranchByName: c + forceCheckoutBranch: F + rebaseBranch: r + renameBranch: R + mergeIntoCurrentBranch: M + viewGitFlowOptions: i + fastForward: f + createTag: T + pushTag: P + setUpstream: u + fetchRemote: f + sortOrder: s + worktrees: + viewWorktreeOptions: w commits: - squashDown: 's' - renameCommit: 'r' - renameCommitWithEditor: 'R' - viewResetOptions: 'g' - markCommitAsFixup: 'f' - createFixupCommit: 'F' # create fixup commit for this commit - squashAboveCommits: 'S' - moveDownCommit: '' # move commit down one - moveUpCommit: '' # move commit up one - amendToCommit: 'A' - amendAttributeMenu: 'a' - pickCommit: 'p' # pick commit (when mid-rebase) - revertCommit: 't' - cherryPickCopy: 'C' - pasteCommits: 'V' - tagCommit: 'T' - checkoutCommit: '' - resetCherryPick: '' - copyCommitMessageToClipboard: '' - openLogMenu: '' - viewBisectOptions: 'b' - stash: - popStash: 'g' - renameStash: 'r' - commitFiles: - checkoutCommitFile: 'c' - main: - toggleSelectHunk: 'a' - pickBothHunks: 'b' - submodules: - init: 'i' - update: 'u' - bulkMenu: 'b' - commitMessage: - commitMenu: '' + squashDown: s + renameCommit: r + renameCommitWithEditor: R + viewResetOptions: g + markCommitAsFixup: f + createFixupCommit: F + squashAboveCommits: S + moveDownCommit: + moveUpCommit: + amendToCommit: A + resetCommitAuthor: a + pickCommit: p + revertCommit: t + cherryPickCopy: C + pasteCommits: V + markCommitAsBaseForRebase: B + tagCommit: T + checkoutCommit: + resetCherryPick: + copyCommitAttributeToClipboard: "y" + openLogMenu: + openInBrowser: o + viewBisectOptions: b + startInteractiveRebase: i amendAttribute: - addCoAuthor: 'c' - resetAuthor: 'a' - setAuthor: 'A' + resetAuthor: a + setAuthor: A + addCoAuthor: c + stash: + popStash: g + renameStash: r + commitFiles: + checkoutCommitFile: c + main: + toggleSelectHunk: a + pickBothHunks: b + editSelectHunk: E + submodules: + init: i + update: u + bulkMenu: b + commitMessage: + commitMenu: + +# Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc +os: + # Command for editing a file. Should contain "{{filename}}". + edit: "" + + # Command for editing a file at a given line number. Should contain + # "{{filename}}", and may optionally contain "{{line}}". + editAtLine: "" + + # Same as EditAtLine, except that the command needs to wait until the + # window is closed. + editAtLineAndWait: "" + + # For opening a directory in an editor + openDirInEditor: "" + + # A built-in preset that sets all of the above settings. Supported presets + # are defined in the getPreset function in editor_presets.go. + editPreset: "" + + # Command for opening a file, as if the file is double-clicked. Should + # contain "{{filename}}", but doesn't support "{{line}}". + open: "" + + # Command for opening a link. Should contain "{{link}}". + openLink: "" + + # EditCommand is the command for editing a file. + # Deprecated: use Edit instead. Note that semantics are different: + # EditCommand is just the command itself, whereas Edit contains a + # "{{filename}}" variable. + editCommand: "" + + # EditCommandTemplate is the command template for editing a file + # Deprecated: use EditAtLine instead. + editCommandTemplate: "" + + # OpenCommand is the command for opening a file + # Deprecated: use Open instead. + openCommand: "" + + # OpenLinkCommand is the command for opening a link + # Deprecated: use OpenLink instead. + openLinkCommand: "" + + # CopyToClipboardCmd is the command for copying to clipboard. + # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-clipboard + copyToClipboardCmd: "" + +# What to do when opening Lazygit outside of a git repo. +# - 'prompt': (default) ask whether to initialize a new repo or open in the most recent repo +# - 'create': initialize a new repo +# - 'skip': open most recent repo +# - 'quit': exit Lazygit +notARepository: prompt + +# If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit. +promptToReturnFromSubprocess: true ``` + ## Platform Defaults diff --git a/go.mod b/go.mod index ca0adebe2..6e7d87686 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/gdamore/tcell/v2 v2.7.4 github.com/go-errors/errors v1.5.1 github.com/gookit/color v1.4.2 + github.com/iancoleman/orderedmap v0.3.0 github.com/imdario/mergo v0.3.11 github.com/integrii/flaggy v1.4.0 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 diff --git a/go.sum b/go.sum index cdd64127c..ae6adb231 100644 --- a/go.sum +++ b/go.sum @@ -171,6 +171,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= diff --git a/pkg/jsonschema/generate_config_docs.go b/pkg/jsonschema/generate_config_docs.go new file mode 100644 index 000000000..441cc55e7 --- /dev/null +++ b/pkg/jsonschema/generate_config_docs.go @@ -0,0 +1,269 @@ +package jsonschema + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "os" + "strings" + + "github.com/iancoleman/orderedmap" + "github.com/jesseduffield/lazycore/pkg/utils" + "github.com/samber/lo" + + "gopkg.in/yaml.v3" +) + +type Node struct { + Name string + Description string + Default any + Children []*Node +} + +const ( + IndentLevel = 2 + DocumentationCommentStart = "\n" + DocumentationCommentEnd = "" + DocumentationCommentStartLen = len(DocumentationCommentStart) +) + +func insertBlankLines(buffer bytes.Buffer) bytes.Buffer { + lines := strings.Split(strings.TrimRight(buffer.String(), "\n"), "\n") + + var newBuffer bytes.Buffer + + previousIndent := -1 + wasComment := false + + for _, line := range lines { + trimmedLine := strings.TrimLeft(line, " ") + indent := len(line) - len(trimmedLine) + isComment := strings.HasPrefix(trimmedLine, "#") + if isComment && !wasComment && indent <= previousIndent { + newBuffer.WriteString("\n") + } + newBuffer.WriteString(line) + newBuffer.WriteString("\n") + previousIndent = indent + wasComment = isComment + } + + return newBuffer +} + +func prepareMarshalledConfig(buffer bytes.Buffer) []byte { + buffer = insertBlankLines(buffer) + + // Remove all `---` lines + lines := strings.Split(strings.TrimRight(buffer.String(), "\n"), "\n") + + var newBuffer bytes.Buffer + + for _, line := range lines { + if strings.TrimSpace(line) != "---" { + newBuffer.WriteString(line) + newBuffer.WriteString("\n") + } + } + + config := newBuffer.Bytes() + + // Add markdown yaml block tag + config = append([]byte("```yaml\n"), config...) + config = append(config, []byte("```\n")...) + + return config +} + +func setComment(yamlNode *yaml.Node, description string) { + // Workaround for the way yaml formats the HeadComment if it contains + // blank lines: it renders these without a leading "#", but we want a + // leading "#" even on blank lines. However, yaml respects it if the + // HeadComment already contains a leading "#", so we prefix all lines + // (including blank ones) with "#". + yamlNode.HeadComment = strings.Join( + lo.Map(strings.Split(description, "\n"), func(s string, _ int) string { + if s == "" { + return "#" // avoid trailing space on blank lines + } + return "# " + s + }), + "\n") +} + +func (n *Node) MarshalYAML() (interface{}, error) { + node := yaml.Node{ + Kind: yaml.MappingNode, + } + + keyNode := yaml.Node{ + Kind: yaml.ScalarNode, + Value: n.Name, + } + if n.Description != "" { + setComment(&keyNode, n.Description) + } + + if n.Default != nil { + valueNode := yaml.Node{ + Kind: yaml.ScalarNode, + } + err := valueNode.Encode(n.Default) + if err != nil { + return nil, err + } + node.Content = append(node.Content, &keyNode, &valueNode) + } else if len(n.Children) > 0 { + childrenNode := yaml.Node{ + Kind: yaml.MappingNode, + } + for _, child := range n.Children { + childYaml, err := child.MarshalYAML() + if err != nil { + return nil, err + } + + childKey := yaml.Node{ + Kind: yaml.ScalarNode, + Value: child.Name, + } + if child.Description != "" { + setComment(&childKey, child.Description) + } + childYaml = childYaml.(*yaml.Node) + childrenNode.Content = append(childrenNode.Content, childYaml.(*yaml.Node).Content...) + } + node.Content = append(node.Content, &keyNode, &childrenNode) + } + + return &node, nil +} + +func getDescription(v *orderedmap.OrderedMap) string { + description, ok := v.Get("description") + if !ok { + description = "" + } + return description.(string) +} + +func getDefault(v *orderedmap.OrderedMap) (error, any) { + defaultValue, ok := v.Get("default") + if ok { + return nil, defaultValue + } + + dataType, ok := v.Get("type") + if ok { + dataTypeString := dataType.(string) + if dataTypeString == "string" { + return nil, "" + } + } + + return errors.New("Failed to get default value"), nil +} + +func parseNode(parent *Node, name string, value *orderedmap.OrderedMap) { + description := getDescription(value) + err, defaultValue := getDefault(value) + if err == nil { + leaf := &Node{Name: name, Description: description, Default: defaultValue} + parent.Children = append(parent.Children, leaf) + } + + properties, ok := value.Get("properties") + if !ok { + return + } + + orderedProperties := properties.(orderedmap.OrderedMap) + + node := &Node{Name: name, Description: description} + parent.Children = append(parent.Children, node) + + keys := orderedProperties.Keys() + for _, name := range keys { + value, _ := orderedProperties.Get(name) + typedValue := value.(orderedmap.OrderedMap) + parseNode(node, name, &typedValue) + } +} + +func writeToConfigDocs(config []byte) error { + configPath := utils.GetLazyRootDirectory() + "/docs/Config.md" + markdown, err := os.ReadFile(configPath) + if err != nil { + return fmt.Errorf("Error reading Config.md file %w", err) + } + + startConfigSectionIndex := bytes.Index(markdown, []byte(DocumentationCommentStart)) + if startConfigSectionIndex == -1 { + return errors.New("Default config starting comment not found") + } + + endConfigSectionIndex := bytes.Index(markdown[startConfigSectionIndex+DocumentationCommentStartLen:], []byte(DocumentationCommentEnd)) + if endConfigSectionIndex == -1 { + return errors.New("Default config closing comment not found") + } + + endConfigSectionIndex = endConfigSectionIndex + startConfigSectionIndex + DocumentationCommentStartLen + + newMarkdown := make([]byte, 0, len(markdown)-endConfigSectionIndex+startConfigSectionIndex+len(config)) + newMarkdown = append(newMarkdown, markdown[:startConfigSectionIndex+DocumentationCommentStartLen]...) + newMarkdown = append(newMarkdown, config...) + newMarkdown = append(newMarkdown, markdown[endConfigSectionIndex:]...) + + if err := os.WriteFile(configPath, newMarkdown, 0o644); err != nil { + return fmt.Errorf("Error writing to file %w", err) + } + return nil +} + +func GenerateConfigDocs() { + content, err := os.ReadFile(GetSchemaDir() + "/config.json") + if err != nil { + panic("Error reading config.json") + } + + schema := orderedmap.New() + + err = json.Unmarshal(content, &schema) + if err != nil { + panic("Failed to unmarshal config.json") + } + + root, ok := schema.Get("properties") + if !ok { + panic("properties key not found in schema") + } + orderedRoot := root.(orderedmap.OrderedMap) + + rootNode := Node{} + for _, name := range orderedRoot.Keys() { + value, _ := orderedRoot.Get(name) + typedValue := value.(orderedmap.OrderedMap) + parseNode(&rootNode, name, &typedValue) + } + + var buffer bytes.Buffer + encoder := yaml.NewEncoder(&buffer) + encoder.SetIndent(IndentLevel) + + for _, child := range rootNode.Children { + err := encoder.Encode(child) + if err != nil { + panic("Failed to Marshal document") + } + } + encoder.Close() + + config := prepareMarshalledConfig(buffer) + + err = writeToConfigDocs(config) + if err != nil { + panic(err) + } +} diff --git a/pkg/jsonschema/generator.go b/pkg/jsonschema/generator.go index 263f4784b..df53dee56 100644 --- a/pkg/jsonschema/generator.go +++ b/pkg/jsonschema/generator.go @@ -11,4 +11,5 @@ import ( func main() { fmt.Printf("Generating jsonschema in %s...\n", jsonschema.GetSchemaDir()) jsonschema.GenerateSchema() + jsonschema.GenerateConfigDocs() } diff --git a/vendor/github.com/iancoleman/orderedmap/LICENSE b/vendor/github.com/iancoleman/orderedmap/LICENSE new file mode 100644 index 000000000..2732e3795 --- /dev/null +++ b/vendor/github.com/iancoleman/orderedmap/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Ian Coleman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, Subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or Substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/iancoleman/orderedmap/orderedmap.go b/vendor/github.com/iancoleman/orderedmap/orderedmap.go new file mode 100644 index 000000000..61587d9c7 --- /dev/null +++ b/vendor/github.com/iancoleman/orderedmap/orderedmap.go @@ -0,0 +1,266 @@ +package orderedmap + +import ( + "bytes" + "encoding/json" + "sort" +) + +type Pair struct { + key string + value interface{} +} + +func (kv *Pair) Key() string { + return kv.key +} + +func (kv *Pair) Value() interface{} { + return kv.value +} + +type ByPair struct { + Pairs []*Pair + LessFunc func(a *Pair, j *Pair) bool +} + +func (a ByPair) Len() int { return len(a.Pairs) } +func (a ByPair) Swap(i, j int) { a.Pairs[i], a.Pairs[j] = a.Pairs[j], a.Pairs[i] } +func (a ByPair) Less(i, j int) bool { return a.LessFunc(a.Pairs[i], a.Pairs[j]) } + +type OrderedMap struct { + keys []string + values map[string]interface{} + escapeHTML bool +} + +func New() *OrderedMap { + o := OrderedMap{} + o.keys = []string{} + o.values = map[string]interface{}{} + o.escapeHTML = true + return &o +} + +func (o *OrderedMap) SetEscapeHTML(on bool) { + o.escapeHTML = on +} + +func (o *OrderedMap) Get(key string) (interface{}, bool) { + val, exists := o.values[key] + return val, exists +} + +func (o *OrderedMap) Set(key string, value interface{}) { + _, exists := o.values[key] + if !exists { + o.keys = append(o.keys, key) + } + o.values[key] = value +} + +func (o *OrderedMap) Delete(key string) { + // check key is in use + _, ok := o.values[key] + if !ok { + return + } + // remove from keys + for i, k := range o.keys { + if k == key { + o.keys = append(o.keys[:i], o.keys[i+1:]...) + break + } + } + // remove from values + delete(o.values, key) +} + +func (o *OrderedMap) Keys() []string { + return o.keys +} + +func (o *OrderedMap) Values() map[string]interface{} { + return o.values +} + +// SortKeys Sort the map keys using your sort func +func (o *OrderedMap) SortKeys(sortFunc func(keys []string)) { + sortFunc(o.keys) +} + +// Sort Sort the map using your sort func +func (o *OrderedMap) Sort(lessFunc func(a *Pair, b *Pair) bool) { + pairs := make([]*Pair, len(o.keys)) + for i, key := range o.keys { + pairs[i] = &Pair{key, o.values[key]} + } + + sort.Sort(ByPair{pairs, lessFunc}) + + for i, pair := range pairs { + o.keys[i] = pair.key + } +} + +func (o *OrderedMap) UnmarshalJSON(b []byte) error { + if o.values == nil { + o.values = map[string]interface{}{} + } + err := json.Unmarshal(b, &o.values) + if err != nil { + return err + } + dec := json.NewDecoder(bytes.NewReader(b)) + if _, err = dec.Token(); err != nil { // skip '{' + return err + } + o.keys = make([]string, 0, len(o.values)) + return decodeOrderedMap(dec, o) +} + +func decodeOrderedMap(dec *json.Decoder, o *OrderedMap) error { + hasKey := make(map[string]bool, len(o.values)) + for { + token, err := dec.Token() + if err != nil { + return err + } + if delim, ok := token.(json.Delim); ok && delim == '}' { + return nil + } + key := token.(string) + if hasKey[key] { + // duplicate key + for j, k := range o.keys { + if k == key { + copy(o.keys[j:], o.keys[j+1:]) + break + } + } + o.keys[len(o.keys)-1] = key + } else { + hasKey[key] = true + o.keys = append(o.keys, key) + } + + token, err = dec.Token() + if err != nil { + return err + } + if delim, ok := token.(json.Delim); ok { + switch delim { + case '{': + if values, ok := o.values[key].(map[string]interface{}); ok { + newMap := OrderedMap{ + keys: make([]string, 0, len(values)), + values: values, + escapeHTML: o.escapeHTML, + } + if err = decodeOrderedMap(dec, &newMap); err != nil { + return err + } + o.values[key] = newMap + } else if oldMap, ok := o.values[key].(OrderedMap); ok { + newMap := OrderedMap{ + keys: make([]string, 0, len(oldMap.values)), + values: oldMap.values, + escapeHTML: o.escapeHTML, + } + if err = decodeOrderedMap(dec, &newMap); err != nil { + return err + } + o.values[key] = newMap + } else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil { + return err + } + case '[': + if values, ok := o.values[key].([]interface{}); ok { + if err = decodeSlice(dec, values, o.escapeHTML); err != nil { + return err + } + } else if err = decodeSlice(dec, []interface{}{}, o.escapeHTML); err != nil { + return err + } + } + } + } +} + +func decodeSlice(dec *json.Decoder, s []interface{}, escapeHTML bool) error { + for index := 0; ; index++ { + token, err := dec.Token() + if err != nil { + return err + } + if delim, ok := token.(json.Delim); ok { + switch delim { + case '{': + if index < len(s) { + if values, ok := s[index].(map[string]interface{}); ok { + newMap := OrderedMap{ + keys: make([]string, 0, len(values)), + values: values, + escapeHTML: escapeHTML, + } + if err = decodeOrderedMap(dec, &newMap); err != nil { + return err + } + s[index] = newMap + } else if oldMap, ok := s[index].(OrderedMap); ok { + newMap := OrderedMap{ + keys: make([]string, 0, len(oldMap.values)), + values: oldMap.values, + escapeHTML: escapeHTML, + } + if err = decodeOrderedMap(dec, &newMap); err != nil { + return err + } + s[index] = newMap + } else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil { + return err + } + } else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil { + return err + } + case '[': + if index < len(s) { + if values, ok := s[index].([]interface{}); ok { + if err = decodeSlice(dec, values, escapeHTML); err != nil { + return err + } + } else if err = decodeSlice(dec, []interface{}{}, escapeHTML); err != nil { + return err + } + } else if err = decodeSlice(dec, []interface{}{}, escapeHTML); err != nil { + return err + } + case ']': + return nil + } + } + } +} + +func (o OrderedMap) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + buf.WriteByte('{') + encoder := json.NewEncoder(&buf) + encoder.SetEscapeHTML(o.escapeHTML) + for i, k := range o.keys { + if i > 0 { + buf.WriteByte(',') + } + // add key + if err := encoder.Encode(k); err != nil { + return nil, err + } + buf.WriteByte(':') + // add value + if err := encoder.Encode(o.values[k]); err != nil { + return nil, err + } + } + buf.WriteByte('}') + return buf.Bytes(), nil +} diff --git a/vendor/github.com/iancoleman/orderedmap/readme.md b/vendor/github.com/iancoleman/orderedmap/readme.md new file mode 100644 index 000000000..cfe09c2fd --- /dev/null +++ b/vendor/github.com/iancoleman/orderedmap/readme.md @@ -0,0 +1,81 @@ +# orderedmap + +[![Build Status](https://travis-ci.com/iancoleman/orderedmap.svg)](https://travis-ci.com/iancoleman/orderedmap) + +A golang data type equivalent to python's collections.OrderedDict + +Retains order of keys in maps + +Can be JSON serialized / deserialized + +# Usage + +```go +package main + +import ( + "encoding/json" + "github.com/iancoleman/orderedmap" +) + +func main() { + + // use New() instead of o := map[string]interface{}{} + o := orderedmap.New() + + // use SetEscapeHTML() to whether escape problematic HTML characters or not, defaults is true + o.SetEscapeHTML(false) + + // use Set instead of o["a"] = 1 + o.Set("a", 1) + + // add some value with special characters + o.Set("b", "\\.<>[]{}_-") + + // use Get instead of i, ok := o["a"] + val, ok := o.Get("a") + + // use Keys instead of for k, v := range o + keys := o.Keys() + for _, k := range keys { + v, _ := o.Get(k) + } + + // use o.Delete instead of delete(o, key) + o.Delete("a") + + // serialize to a json string using encoding/json + bytes, err := json.Marshal(o) + prettyBytes, err := json.MarshalIndent(o, "", " ") + + // deserialize a json string using encoding/json + // all maps (including nested maps) will be parsed as orderedmaps + s := `{"a": 1}` + err := json.Unmarshal([]byte(s), &o) + + // sort the keys + o.SortKeys(sort.Strings) + + // sort by Pair + o.Sort(func(a *orderedmap.Pair, b *orderedmap.Pair) bool { + return a.Value().(float64) < b.Value().(float64) + }) +} +``` + +# Caveats + +* OrderedMap only takes strings for the key, as per [the JSON spec](http://json.org/). + +# Tests + +``` +go test +``` + +# Alternatives + +None of the alternatives offer JSON serialization. + +* [cevaris/ordered_map](https://github.com/cevaris/ordered_map) +* [mantyr/iterator](https://github.com/mantyr/iterator) diff --git a/vendor/modules.txt b/vendor/modules.txt index 425c1705b..881770146 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -110,6 +110,9 @@ github.com/gobwas/glob/util/strings # github.com/gookit/color v1.4.2 ## explicit; go 1.12 github.com/gookit/color +# github.com/iancoleman/orderedmap v0.3.0 +## explicit; go 1.16 +github.com/iancoleman/orderedmap # github.com/imdario/mergo v0.3.11 ## explicit; go 1.13 github.com/imdario/mergo