diff --git a/doc-values/extra-values.go b/doc-values/extra-values.go index 4b224f2..3a47e9c 100644 --- a/doc-values/extra-values.go +++ b/doc-values/extra-values.go @@ -159,6 +159,12 @@ func PositiveNumberValue() Value { } } +func MaskValue() Value { + min := 0 + max := 777 + return NumberValue{Min: &min, Max: &max} +} + func SingleEnumValue(value string) EnumValue { return EnumValue{ EnforceValues: true, diff --git a/doc-values/utils.go b/doc-values/utils.go index 772e215..68e7326 100644 --- a/doc-values/utils.go +++ b/doc-values/utils.go @@ -29,7 +29,7 @@ func MergeKeyEnumAssignmentMaps(maps ...map[EnumString]Value) map[EnumString]Val for _, m := range maps { for key, value := range m { - if _, ok := existingEnums[key.InsertText]; !ok { + if _, ok := existingEnums[key.InsertText]; ok { continue } diff --git a/doc-values/value-array.go b/doc-values/value-array.go index 23fc486..94a59ff 100644 --- a/doc-values/value-array.go +++ b/doc-values/value-array.go @@ -122,8 +122,8 @@ func (v ArrayValue) FetchCompletions(line string, cursor uint32) []protocol.Comp relativePosition, found := utils.FindPreviousCharacter(line, v.Separator, int(cursor-1)) if found { - line = line[uint32(relativePosition):] - cursor -= uint32(relativePosition) + line = line[uint32(relativePosition+1):] + cursor -= uint32(relativePosition + 1) } return v.SubValue.FetchCompletions(line, cursor) diff --git a/doc-values/value-enum.go b/doc-values/value-enum.go index b180bf9..8e5cd98 100644 --- a/doc-values/value-enum.go +++ b/doc-values/value-enum.go @@ -30,6 +30,17 @@ type EnumString struct { Documentation string } +func (v EnumString) ToCompletionItem() protocol.CompletionItem { + textFormat := protocol.InsertTextFormatPlainText + kind := protocol.CompletionItemKindEnum + return protocol.CompletionItem{ + Label: v.InsertText, + InsertTextFormat: &textFormat, + Kind: &kind, + Documentation: &v.Documentation, + } +} + func CreateEnumString(value string) EnumString { return EnumString{ InsertText: value, diff --git a/doc-values/value-gid.go b/doc-values/value-gid.go new file mode 100644 index 0000000..5fca78c --- /dev/null +++ b/doc-values/value-gid.go @@ -0,0 +1,128 @@ +package docvalues + +import ( + "config-lsp/utils" + "strconv" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +type GIDNotInPasswdErr struct{} + +func (e GIDNotInPasswdErr) Error() string { + return "This UID does not exist" +} + +type InvalidGIDError struct{} + +func (e InvalidGIDError) Error() string { + return "This UID is invalid" +} + +type GIDNotInRangeError struct{} + +func (e GIDNotInRangeError) Error() string { + return "UIDs must be between 0 and 65535" +} + +type GIDValue struct { + EnforceUsingExisting bool +} + +func (v GIDValue) GetTypeDescription() []string { + return []string{"Group ID"} +} + +func (v GIDValue) CheckIsValid(value string) []*InvalidValue { + uid, err := strconv.Atoi(value) + + if err != nil { + return []*InvalidValue{{ + Err: InvalidGIDError{}, + Start: 0, + End: uint32(len(value)), + }} + } + + if uid < 0 || uid > 65535 { + return []*InvalidValue{{ + Err: GIDNotInRangeError{}, + Start: 0, + End: uint32(len(value)), + }} + } + + if v.EnforceUsingExisting { + infos, err := fetchPasswdInfo() + + if err != nil { + return []*InvalidValue{} + } + + found := false + + for _, info := range infos { + if info.GID == value { + found = true + break + } + } + + if !found { + return []*InvalidValue{{ + Err: GIDNotInPasswdErr{}, + Start: 0, + End: uint32(len(value)), + }} + } + } + + return []*InvalidValue{} +} + +var defaultGIDsExplanation = []EnumString{ + { + InsertText: "0", + DescriptionText: "root", + Documentation: "The group of the root user", + }, +} + +func (v GIDValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { + infos, err := fetchGroupInfo() + + if err != nil { + return utils.Map(defaultUIDsExplanation, func(enum EnumString) protocol.CompletionItem { + return enum.ToCompletionItem() + }) + } + + textFormat := protocol.InsertTextFormatPlainText + kind := protocol.CompletionItemKindValue + completions := make([]protocol.CompletionItem, len(infos)) + + for index, info := range infos { + // Find default gids + var existingGID *EnumString + + for _, defaultGID := range defaultUIDsExplanation { + if defaultGID.InsertText == info.GID { + existingGID = &defaultGID + break + } + } + + if existingGID != nil { + completions[index] = existingGID.ToCompletionItem() + } else { + completions[index] = protocol.CompletionItem{ + InsertTextFormat: &textFormat, + Kind: &kind, + InsertText: &info.GID, + Documentation: info.Name, + } + } + } + + return completions +} diff --git a/doc-values/value-key-enum-assignment.go b/doc-values/value-key-enum-assignment.go index 226583d..3f69a13 100644 --- a/doc-values/value-key-enum-assignment.go +++ b/doc-values/value-key-enum-assignment.go @@ -111,13 +111,23 @@ func (v KeyEnumAssignmentValue) FetchEnumCompletions() []protocol.CompletionItem for enumKey := range v.Values { textFormat := protocol.InsertTextFormatPlainText - kind := protocol.CompletionItemKindEnum + kind := protocol.CompletionItemKindField + val := v.Values[enumKey] + description := val.GetTypeDescription() + + var documentation string + + if len(description) == 1 { + documentation = fmt.Sprintf("%s%s<%s> \n\n%s", enumKey.InsertText, v.Separator, description[0], enumKey.Documentation) + } else { + documentation = fmt.Sprintf("%s%s \n\n%s", enumKey.InsertText, v.Separator, enumKey.Documentation) + } completions = append(completions, protocol.CompletionItem{ Label: enumKey.InsertText, InsertTextFormat: &textFormat, Kind: &kind, - Documentation: &enumKey.Documentation, + Documentation: documentation, }) } diff --git a/doc-values/value-or.go b/doc-values/value-or.go index 296d7eb..0e9a6e5 100644 --- a/doc-values/value-or.go +++ b/doc-values/value-or.go @@ -1,6 +1,7 @@ package docvalues import ( + "config-lsp/utils" "strings" protocol "github.com/tliron/glsp/protocol_3_16" @@ -49,6 +50,26 @@ func (v OrValue) CheckIsValid(value string) []*InvalidValue { return errors } func (v OrValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { + // Check for special cases + if len(v.Values) == 2 { + switch v.Values[0].(type) { + case KeyEnumAssignmentValue: + if cursor > 0 { + // KeyEnumAssignment + other values + // If there is an separator, we only want to show + // the values of the KeyEnumAssignment + keyEnumValue := v.Values[0].(KeyEnumAssignmentValue) + + println("line eta cursor", line, cursor) + _, found := utils.FindPreviousCharacter(line, keyEnumValue.Separator, int(cursor-1)) + + if found { + return keyEnumValue.FetchCompletions(line, cursor) + } + } + } + } + completions := make([]protocol.CompletionItem, 0) for _, subValue := range v.Values { diff --git a/doc-values/value-power-of-two.go b/doc-values/value-power-of-two.go index 8cf1d12..75bba30 100644 --- a/doc-values/value-power-of-two.go +++ b/doc-values/value-power-of-two.go @@ -62,7 +62,7 @@ var powers = []int{1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192 func (v PowerOfTwoValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { textFormat := protocol.InsertTextFormatPlainText - kind := protocol.CompletionItemKindEnum + kind := protocol.CompletionItemKindValue return utils.Map( powers, diff --git a/doc-values/value-regex.go b/doc-values/value-regex.go index 2d5fbfc..2be9481 100644 --- a/doc-values/value-regex.go +++ b/doc-values/value-regex.go @@ -26,16 +26,15 @@ func (v RegexValue) GetTypeDescription() []string { } func (v RegexValue) CheckIsValid(value string) []*InvalidValue { - if value == "" { + if !v.Regex.MatchString(value) { return []*InvalidValue{{ - Err: EmptyStringError{}, + Err: RegexInvalidError{Regex: v.Regex.String()}, Start: 0, End: uint32(len(value)), - }, - } + }} } - return nil + return []*InvalidValue{} } func (v RegexValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { diff --git a/doc-values/value-string.go b/doc-values/value-string.go index f5f8b7c..67b163e 100644 --- a/doc-values/value-string.go +++ b/doc-values/value-string.go @@ -10,7 +10,15 @@ func (e EmptyStringError) Error() string { return "This setting may not be empty" } -type StringValue struct{} +type StringTooLongError struct{} + +func (e StringTooLongError) Error() string { + return "This setting is too long" +} + +type StringValue struct { + MaxLength *uint32 +} func (v StringValue) GetTypeDescription() []string { return []string{"String"} @@ -26,6 +34,14 @@ func (v StringValue) CheckIsValid(value string) []*InvalidValue { } } + if v.MaxLength != nil && uint32(len(value)) > *v.MaxLength { + return []*InvalidValue{{ + Err: StringTooLongError{}, + Start: 0, + End: uint32(len(value)), + }} + } + return nil } diff --git a/doc-values/value-uid.go b/doc-values/value-uid.go new file mode 100644 index 0000000..bfc436e --- /dev/null +++ b/doc-values/value-uid.go @@ -0,0 +1,129 @@ +package docvalues + +import ( + "config-lsp/utils" + "fmt" + "strconv" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +type UIDNotInPasswdErr struct{} + +func (e UIDNotInPasswdErr) Error() string { + return "This UID does not exist" +} + +type InvalidUIDError struct{} + +func (e InvalidUIDError) Error() string { + return "This UID is invalid" +} + +type UIDNotInRangeError struct{} + +func (e UIDNotInRangeError) Error() string { + return "UIDs must be between 0 and 65535" +} + +type UIDValue struct { + EnforceUsingExisting bool +} + +func (v UIDValue) GetTypeDescription() []string { + return []string{"User ID"} +} + +func (v UIDValue) CheckIsValid(value string) []*InvalidValue { + uid, err := strconv.Atoi(value) + + if err != nil { + return []*InvalidValue{{ + Err: InvalidUIDError{}, + Start: 0, + End: uint32(len(value)), + }} + } + + if uid < 0 || uid > 65535 { + return []*InvalidValue{{ + Err: UIDNotInRangeError{}, + Start: 0, + End: uint32(len(value)), + }} + } + + if v.EnforceUsingExisting { + infos, err := fetchPasswdInfo() + + if err != nil { + return []*InvalidValue{} + } + + found := false + + for _, info := range infos { + if info.UID == value { + found = true + break + } + } + + if !found { + return []*InvalidValue{{ + Err: UIDNotInPasswdErr{}, + Start: 0, + End: uint32(len(value)), + }} + } + } + + return []*InvalidValue{} +} + +var defaultUIDsExplanation = []EnumString{ + { + InsertText: "0", + DescriptionText: "root", + Documentation: "The root user", + }, +} + +func (v UIDValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { + infos, err := fetchPasswdInfo() + + if err != nil { + return utils.Map(defaultUIDsExplanation, func(enum EnumString) protocol.CompletionItem { + return enum.ToCompletionItem() + }) + } + + textFormat := protocol.InsertTextFormatPlainText + kind := protocol.CompletionItemKindValue + completions := make([]protocol.CompletionItem, len(infos)) + + for index, info := range infos { + // Find default uids + var existingUID *EnumString + + for _, defaultUID := range defaultUIDsExplanation { + if defaultUID.InsertText == info.UID { + existingUID = &defaultUID + break + } + } + + if existingUID != nil { + completions[index] = existingUID.ToCompletionItem() + } else { + completions[index] = protocol.CompletionItem{ + InsertTextFormat: &textFormat, + Kind: &kind, + InsertText: &info.UID, + Documentation: fmt.Sprintf("User %s; Home: %s", info.Name, info.HomePath), + } + } + } + + return completions +}