summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/config/user_config.go27
-rw-r--r--pkg/jsonschema/generate_config_docs.go269
-rw-r--r--pkg/jsonschema/generator.go1
3 files changed, 284 insertions, 13 deletions
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index c22594461..2df6e60b8 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -19,8 +19,6 @@ type UserConfig struct {
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
// If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close
QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"`
- // Keybindings
- Keybinding KeybindingConfig `yaml:"keybinding"`
// Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc
OS OSConfig `yaml:"os,omitempty"`
// If true, don't display introductory popups upon opening Lazygit.
@@ -38,6 +36,8 @@ type UserConfig struct {
NotARepository string `yaml:"notARepository" jsonschema:"enum=prompt,enum=create,enum=skip,enum=quit"`
// If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit.
PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"`
+ // Keybindings
+ Keybinding KeybindingConfig `yaml:"keybinding"`
}
type RefresherConfig struct {
@@ -252,7 +252,7 @@ type PagingConfig struct {
// diff-so-fancy
// delta --dark --paging=never
// ydiff -p cat -s --wrap --width={{columnWidth}}
- Pager PagerType `yaml:"pager" jsonschema:"minLength=1"`
+ Pager PagerType `yaml:"pager"`
// If true, Lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
UseConfig bool `yaml:"useConfig"`
// e.g. 'difft --color=always'
@@ -294,9 +294,9 @@ type LogConfig struct {
type CommitPrefixConfig struct {
// pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*"
- Pattern string `yaml:"pattern" jsonschema:"example=^\\w+\\/(\\w+-\\w+).*,minLength=1"`
+ Pattern string `yaml:"pattern" jsonschema:"example=^\\w+\\/(\\w+-\\w+).*"`
// Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] "
- Replace string `yaml:"replace" jsonschema:"example=[$1] ,minLength=1"`
+ Replace string `yaml:"replace" jsonschema:"example=[$1]"`
}
type UpdateConfig struct {
@@ -684,6 +684,7 @@ func GetDefaultConfig() *UserConfig {
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
+ WindowSize: "normal",
Border: "rounded",
AnimateExplosion: true,
PortraitMode: "auto",
@@ -735,8 +736,14 @@ func GetDefaultConfig() *UserConfig {
Method: "prompt",
Days: 14,
},
- ConfirmOnQuit: false,
- QuitOnTopLevelReturn: false,
+ ConfirmOnQuit: false,
+ QuitOnTopLevelReturn: false,
+ OS: OSConfig{},
+ DisableStartupPopups: false,
+ CustomCommands: []CustomCommand(nil),
+ Services: map[string]string(nil),
+ NotARepository: "prompt",
+ PromptToReturnFromSubprocess: true,
Keybinding: KeybindingConfig{
Universal: KeybindingUniversalConfig{
Quit: "q",
@@ -903,11 +910,5 @@ func GetDefaultConfig() *UserConfig {
CommitMenu: "<c-o>",
},
},
- OS: OSConfig{},
- DisableStartupPopups: false,
- CustomCommands: []CustomCommand(nil),
- Services: map[string]string(nil),
- NotARepository: "prompt",
- PromptToReturnFromSubprocess: true,
}
}
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 = "<!-- START CONFIG YAML: AUTOMATICALLY GENERATED with `go generate ./..., DO NOT UPDATE MANUALLY -->\n"
+ DocumentationCommentEnd = "<!-- END CONFIG YAML -->"
+ 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()
}