diff --git a/handlers/fstab/documentation/documentation-spec.go b/handlers/fstab/documentation/documentation-spec.go index 63ad93e..26a0901 100644 --- a/handlers/fstab/documentation/documentation-spec.go +++ b/handlers/fstab/documentation/documentation-spec.go @@ -17,33 +17,14 @@ var SpecField = docvalues.OrValue{ // docvalues.PathValue{ // RequiredType: docvalues.PathTypeFile & docvalues.PathTypeExistenceOptional, // }, - docvalues.KeyValueAssignmentValue{ + docvalues.KeyEnumAssignmentValue{ Separator: "=", ValueIsOptional: false, - Key: docvalues.EnumValue{ - EnforceValues: true, - Values: []docvalues.EnumString{ - docvalues.CreateEnumString("UUID"), - docvalues.CreateEnumString("LABEL"), - docvalues.CreateEnumString("PARTUUID"), - docvalues.CreateEnumString("PARTLABEL"), - }, - }, - Value: docvalues.CustomValue{ - FetchValue: func(rawContext docvalues.CustomValueContext) docvalues.Value { - context := rawContext.(docvalues.KeyValueAssignmentContext) - - switch context.SelectedKey { - case "UUID": - case "PARTUUID": - return UuidField - case "LABEL": - case "PARTLABEL": - return LabelField - } - - return docvalues.StringValue{} - }, + Values: map[docvalues.EnumString]docvalues.Value{ + docvalues.CreateEnumString("UUID"): UuidField, + docvalues.CreateEnumString("PARTUUID"): UuidField, + docvalues.CreateEnumString("LABEL"): LabelField, + docvalues.CreateEnumString("PARTLABEL"): LabelField, }, }, }, diff --git a/handlers/fstab/parser.go b/handlers/fstab/parser.go index 0922101..ff81d55 100644 --- a/handlers/fstab/parser.go +++ b/handlers/fstab/parser.go @@ -44,6 +44,17 @@ func (f *Field) CreateRange(fieldLine uint32) protocol.Range { } } +type FstabField string + +const ( + FstabFieldSpec FstabField = "spec" + FstabFieldMountPoint FstabField = "mountpoint" + FstabFieldFileSystemType FstabField = "filesystemtype" + FstabFieldOptions FstabField = "options" + FstabFieldFreq FstabField = "freq" + FstabFieldPass FstabField = "pass" +) + type FstabFields struct { Spec *Field MountPoint *Field @@ -146,6 +157,30 @@ func (e *FstabEntry) CheckIsValid() []protocol.Diagnostic { return diagnostics } +func (e FstabEntry) GetFieldAtPosition(cursor uint32) FstabField { + if e.Fields.Spec == nil || (cursor >= e.Fields.Spec.Start && cursor <= e.Fields.Spec.End) { + return FstabFieldSpec + } + + if e.Fields.MountPoint == nil || (cursor >= e.Fields.MountPoint.Start && cursor <= e.Fields.MountPoint.End) { + return FstabFieldMountPoint + } + + if e.Fields.FilesystemType == nil || (cursor >= e.Fields.FilesystemType.Start && cursor <= e.Fields.FilesystemType.End) { + return FstabFieldFileSystemType + } + + if e.Fields.Options == nil || (cursor >= e.Fields.Options.Start && cursor <= e.Fields.Options.End) { + return FstabFieldOptions + } + + if e.Fields.Freq == nil || (cursor >= e.Fields.Freq.Start && cursor <= e.Fields.Freq.End) { + return FstabFieldFreq + } + + return FstabFieldPass +} + type FstabParser struct { entries []FstabEntry } @@ -157,83 +192,83 @@ func (p *FstabParser) AddLine(line string, lineNumber int) error { return MalformedLineError{} } - var spec Field - var mountPoint Field - var filesystemType Field - var options Field - var freq Field - var pass Field + var spec *Field + var mountPoint *Field + var filesystemType *Field + var options *Field + var freq *Field + var pass *Field switch len(fields) { case 6: value := fields[5] start := uint32(strings.Index(line, value)) - pass = Field{ + pass = &Field{ Value: fields[5], Start: start, - End: start + uint32(len(value)), + End: start + uint32(len(value)) - 1, } fallthrough case 5: value := fields[4] start := uint32(strings.Index(line, value)) - freq = Field{ + freq = &Field{ Value: value, Start: start, - End: start + uint32(len(value)), + End: start + uint32(len(value)) - 1, } fallthrough case 4: value := fields[3] start := uint32(strings.Index(line, value)) - options = Field{ + options = &Field{ Value: value, Start: start, - End: start + uint32(len(value)), + End: start + uint32(len(value)) - 1, } fallthrough case 3: value := fields[2] start := uint32(strings.Index(line, value)) - filesystemType = Field{ + filesystemType = &Field{ Value: value, Start: start, - End: start + uint32(len(value)), + End: start + uint32(len(value)) - 1, } fallthrough case 2: value := fields[1] start := uint32(strings.Index(line, value)) - mountPoint = Field{ + mountPoint = &Field{ Value: value, Start: start, - End: start + uint32(len(value)), + End: start + uint32(len(value)) - 1, } fallthrough case 1: value := fields[0] start := uint32(strings.Index(line, value)) - spec = Field{ + spec = &Field{ Value: value, Start: start, - End: start + uint32(len(value)), + End: start + uint32(len(value)) - 1, } } entry := FstabEntry{ Line: uint32(lineNumber), Fields: FstabFields{ - Spec: &spec, - MountPoint: &mountPoint, - FilesystemType: &filesystemType, - Options: &options, - Freq: &freq, - Pass: &pass, + Spec: spec, + MountPoint: mountPoint, + FilesystemType: filesystemType, + Options: options, + Freq: freq, + Pass: pass, }, } p.entries = append(p.entries, entry) @@ -263,7 +298,7 @@ func (p *FstabParser) ParseFromContent(content string) []common.ParseError { return errors } -func (p *FstabParser) GetEntry(line uint32) (FstabEntry, bool) { +func (p *FstabParser) GetEntry(line uint32) (*FstabEntry, bool) { index, found := slices.BinarySearchFunc(p.entries, line, func(entry FstabEntry, line uint32) int { if entry.Line < line { return -1 @@ -277,10 +312,10 @@ func (p *FstabParser) GetEntry(line uint32) (FstabEntry, bool) { }) if !found { - return FstabEntry{}, false + return nil, false } - return p.entries[index], true + return &p.entries[index], true } func (p *FstabParser) Clear() { diff --git a/handlers/fstab/text-document-completion.go b/handlers/fstab/text-document-completion.go new file mode 100644 index 0000000..ff800cf --- /dev/null +++ b/handlers/fstab/text-document-completion.go @@ -0,0 +1,102 @@ +package fstab + +import ( + docvalues "config-lsp/doc-values" + fstabdocumentation "config-lsp/handlers/fstab/documentation" + "fmt" + + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { + parser := documentParserMap[params.TextDocument.URI] + + entry, found := parser.GetEntry(params.Position.Line) + + if !found { + // Empty line, return spec completions + return fstabdocumentation.SpecField.FetchCompletions( + "", + params.Position.Character, + ), nil + } + + cursor := params.Position.Character + targetField := entry.GetFieldAtPosition(cursor - 1) + + println("cursor at", cursor, "target field", targetField) + + switch targetField { + case FstabFieldSpec: + value, cursor := GetFieldSafely(entry.Fields.Spec, cursor) + + return fstabdocumentation.SpecField.FetchCompletions( + value, + cursor, + ), nil + case FstabFieldMountPoint: + value, cursor := GetFieldSafely(entry.Fields.MountPoint, cursor) + + return fstabdocumentation.MountPointField.FetchCompletions( + value, + cursor, + ), nil + case FstabFieldFileSystemType: + println(fmt.Sprintf("file system type: %s", entry.Fields.FilesystemType)) + value, cursor := GetFieldSafely(entry.Fields.FilesystemType, cursor) + println("CURSOR", cursor) + + return fstabdocumentation.FileSystemTypeField.FetchCompletions( + value, + cursor, + ), nil + case FstabFieldOptions: + fileSystemType := entry.Fields.FilesystemType.Value + + var optionsField docvalues.Value + + if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found { + optionsField = foundField + } else { + optionsField = fstabdocumentation.DefaultMountOptionsField + } + + value, cursor := GetFieldSafely(entry.Fields.Options, cursor) + + return optionsField.FetchCompletions( + value, + cursor, + ), nil + case FstabFieldFreq: + value, cursor := GetFieldSafely(entry.Fields.Freq, cursor) + + return fstabdocumentation.FreqField.FetchCompletions( + value, + cursor, + ), nil + case FstabFieldPass: + value, cursor := GetFieldSafely(entry.Fields.Pass, cursor) + + return fstabdocumentation.PassField.FetchCompletions( + value, + cursor, + ), nil + } + + return nil, nil +} + +// Safely get value and new cursor position +// If field is nil, return empty string and 0 +func GetFieldSafely(field *Field, character uint32) (string, uint32) { + if field == nil { + return "", 0 + } + + if field.Value == "" { + return "", 0 + } + + return field.Value, character - field.Start +} diff --git a/handlers/fstab/text-document-did-open.go b/handlers/fstab/text-document-did-open.go index 215fcc0..2380cb9 100644 --- a/handlers/fstab/text-document-did-open.go +++ b/handlers/fstab/text-document-did-open.go @@ -15,7 +15,6 @@ func TextDocumentDidOpen( common.ClearDiagnostics(context, params.TextDocument.URI) parser := FstabParser{} - documentParserMap[params.TextDocument.URI] = &parser errors := parser.ParseFromContent(params.TextDocument.Text)