diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go index 3da36549..0a052107 100644 --- a/internal/lsp/hover.go +++ b/internal/lsp/hover.go @@ -6,7 +6,6 @@ import ( "reflect" "github.com/caixw/apidoc/v7/core" - "github.com/caixw/apidoc/v7/internal/ast" "github.com/caixw/apidoc/v7/internal/lsp/protocol" ) @@ -17,22 +16,6 @@ type usager interface { var usagerType = reflect.TypeOf((*usager)(nil)).Elem() -func hover(doc *ast.APIDoc, uri core.URI, pos core.Position, h *protocol.Hover) { - u := doc.Search(uri, pos, usagerType) - if u == nil { - return - } - - usage := u.(usager) - if v := usage.Usage(); v != "" { - h.Range = usage.Loc().Range - h.Contents = protocol.MarkupContent{ - Kind: protocol.MarkupKindMarkdown, - Value: v, - } - } -} - // textDocument/hover // // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover @@ -46,6 +29,15 @@ func (s *server) textDocumentHover(notify bool, in *protocol.HoverParams, out *p f.parsedMux.RLock() defer f.parsedMux.RUnlock() - hover(f.doc, in.TextDocument.URI, in.TextDocumentPositionParams.Position, out) + if u := f.doc.Search(in.TextDocument.URI, in.TextDocumentPositionParams.Position, usagerType); u != nil { + usage := u.(usager) + if v := usage.Usage(); v != "" { + out.Range = usage.Loc().Range + out.Contents = protocol.MarkupContent{ + Kind: protocol.MarkupKindMarkdown, + Value: v, + } + } + } return nil } diff --git a/internal/lsp/hover_test.go b/internal/lsp/hover_test.go index 99c3e2e5..7d2571e3 100644 --- a/internal/lsp/hover_test.go +++ b/internal/lsp/hover_test.go @@ -16,7 +16,13 @@ import ( "github.com/caixw/apidoc/v7/internal/lsp/protocol" ) -func loadHoverDoc(a *assert.Assertion) *ast.APIDoc { +func TestServer_textDocumentHover(t *testing.T) { + a := assert.New(t) + s := newTestServer(true, log.New(ioutil.Discard, "", 0), log.New(ioutil.Discard, "", 0)) + h := &protocol.Hover{} + err := s.textDocumentHover(false, &protocol.HoverParams{}, h) + a.Nil(err) + const b = ` 标题 xml @@ -30,7 +36,6 @@ func loadHoverDoc(a *assert.Assertion) *ast.APIDoc { ` - blk := core.Block{Data: []byte(b), Location: core.Location{URI: "file:///test/doc.go"}} rslt := messagetest.NewMessageHandler() doc := &ast.APIDoc{} @@ -38,20 +43,10 @@ func loadHoverDoc(a *assert.Assertion) *ast.APIDoc { rslt.Handler.Stop() a.Empty(rslt.Errors) - return doc -} - -func TestServer_textDocumentHover(t *testing.T) { - a := assert.New(t) - s := newTestServer(true, log.New(ioutil.Discard, "", 0), log.New(ioutil.Discard, "", 0)) - h := &protocol.Hover{} - err := s.textDocumentHover(false, &protocol.HoverParams{}, h) - a.Nil(err) - s.folders = []*folder{ { WorkspaceFolder: protocol.WorkspaceFolder{Name: "test", URI: "file:///test"}, - doc: loadHoverDoc(a), + doc: doc, }, } @@ -67,58 +62,3 @@ func TestServer_textDocumentHover(t *testing.T) { }) a.Equal(h.Contents.Value, locale.Sprintf("usage-apidoc-title")) } - -func TestHover(t *testing.T) { - a := assert.New(t) - - doc := loadHoverDoc(a) - - // 超出范围 - h := &protocol.Hover{} - pos := core.Position{Line: 1000, Character: 1} - hover(doc, core.URI("file:///test/doc.go"), pos, h) - a.True(h.Range.IsEmpty()).Empty(h.Contents.Value) - - // title - h = &protocol.Hover{} - pos = core.Position{Line: 1, Character: 1} - hover(doc, core.URI("file:///test/doc.go"), pos, h) - a.Equal(h.Range, core.Range{ - Start: core.Position{Line: 1, Character: 1}, - End: core.Position{Line: 1, Character: 18}, - }) - a.Equal(h.Contents.Value, locale.Sprintf("usage-apidoc-title")) - - // apis[0] - h = &protocol.Hover{} - pos = core.Position{Line: 4, Character: 2} - hover(doc, core.URI("file:///test/doc.go"), pos, h) - a.Equal(h.Range, core.Range{ - Start: core.Position{Line: 4, Character: 1}, - End: core.Position{Line: 7, Character: 7}, - }) - a.Equal(h.Contents.Value, locale.Sprintf("usage-apidoc-apis")) - - // 改变了 api[0].URI - doc.APIs[0].URI = core.URI("api0.go") - - // 改变了 api[0].URI,不再匹配 apis[0],取其父元素 apidoc - h = &protocol.Hover{} - pos = core.Position{Line: 4, Character: 1} - hover(doc, core.URI("file:///test/doc.go"), pos, h) - a.Equal(h.Range, core.Range{ - Start: core.Position{Line: 0, Character: 0}, - End: core.Position{Line: 12, Character: 9}, - }) - a.Equal(h.Contents.Value, locale.Sprintf("usage-apidoc")) - - // 与 apis[0] 相同的 URI - h = &protocol.Hover{} - pos = core.Position{Line: 4, Character: 1} - hover(doc, core.URI("api0.go"), pos, h) - a.Equal(h.Range, core.Range{ - Start: core.Position{Line: 4, Character: 1}, - End: core.Position{Line: 7, Character: 7}, - }) - a.Equal(h.Contents.Value, locale.Sprintf("usage-apidoc-apis")) -} diff --git a/internal/lsp/reference.go b/internal/lsp/reference.go index 5e9f9d2c..f004c12e 100644 --- a/internal/lsp/reference.go +++ b/internal/lsp/reference.go @@ -46,7 +46,9 @@ func (s *server) textDocumentDefinition(notify bool, in *protocol.DefinitionPara f.parsedMux.RLock() defer f.parsedMux.RUnlock() - *out = definition(f.doc, in.TextDocument.URI, in.Position) + if r := f.doc.Search(in.TextDocument.URI, in.TextDocumentPositionParams.Position, definitionerType); r != nil { + *out = []core.Location{r.(ast.Definitioner).Definition().Location} + } return nil } @@ -67,12 +69,3 @@ func references(doc *ast.APIDoc, uri core.URI, pos core.Position, include bool) return } - -func definition(doc *ast.APIDoc, uri core.URI, pos core.Position) []core.Location { - r := doc.Search(uri, pos, definitionerType) - if r == nil { - return []core.Location{} - } - - return []core.Location{r.(ast.Definitioner).Definition().Location} -} diff --git a/internal/lsp/reference_test.go b/internal/lsp/reference_test.go index f09b6db8..ba9e41fa 100644 --- a/internal/lsp/reference_test.go +++ b/internal/lsp/reference_test.go @@ -123,40 +123,3 @@ func TestReferences(t *testing.T) { locs = references(doc, "file:///root/doc.go", pos, true) a.Equal(len(locs), 3) } - -func TestDefinition(t *testing.T) { - a := assert.New(t) - doc := loadReferencesDoc(a) - - pos := core.Position{} - locs := definition(doc, "file:///root/doc.go", pos) - a.Empty(locs) - - pos = core.Position{Line: 3, Character: 16} - locs = definition(doc, "file:///root/doc.go", pos) - a.Empty(locs) - - pos = core.Position{Line: 6, Character: 2} - locs = definition(doc, "file:///root/doc.go", pos) - a.Equal(locs, []core.Location{ - { - URI: "file:///root/doc.go", - Range: core.Range{ - Start: core.Position{Line: 3, Character: 1}, - End: core.Position{Line: 3, Character: 31}, - }, - }, - }) - - pos = core.Position{Line: 12, Character: 2} - locs = definition(doc, "file:///root/doc.go", pos) - a.Equal(locs, []core.Location{ - { - URI: "file:///root/doc.go", - Range: core.Range{ - Start: core.Position{Line: 4, Character: 1}, - End: core.Position{Line: 4, Character: 31}, - }, - }, - }) -} diff --git a/internal/xmlenc/decode.go b/internal/xmlenc/decode.go index 29a21e57..7d963d8b 100644 --- a/internal/xmlenc/decode.go +++ b/internal/xmlenc/decode.go @@ -107,9 +107,9 @@ func Decode(p *Parser, v interface{}, namespace string) { switch elem := t.(type) { case *StartElement: if hasRoot { // 多个根元素 - _ = p.endElement(elem) // 找到对应的结束标签,忽略错误 - msg := p.NewError(elem.Location.Range.Start, p.Current().Position, elem.Name.String(), locale.ErrMultipleRootTag). - AddTypes(core.ErrorTypeUnused) + p.endElement(elem) // 找到对应的结束标签,忽略错误 + msg := core.NewError(locale.ErrMultipleRootTag).AddTypes(core.ErrorTypeUnused).WithField(elem.Name.String()). + WithLocation(core.Location{URI: elem.URI, Range: core.Range{Start: elem.Location.Range.Start, End: p.Current().Position}}) d.p.Warning(msg) return } @@ -155,19 +155,19 @@ func (d *decoder) decode(n *node.Node, start *StartElement) *EndElement { func (d *decoder) checkOmitempty(n *node.Node, start, end core.Position, field string) { for _, attr := range n.Attributes { if canNotEmpty(attr) { - d.p.Error(d.p.NewError(start, end, attr.Name, locale.ErrIsEmpty, attr.Name)) + d.p.Error(d.p.newError(start, end, attr.Name, locale.ErrIsEmpty, attr.Name)) } } for _, elem := range n.Elements { if canNotEmpty(elem) { - d.p.Error(d.p.NewError(start, end, elem.Name, locale.ErrIsEmpty, elem.Name)) + d.p.Error(d.p.newError(start, end, elem.Name, locale.ErrIsEmpty, elem.Name)) } } if n.CData != nil && canNotEmpty(n.CData) { - d.p.Error(d.p.NewError(start, end, "cdata", locale.ErrIsEmpty, field)) + d.p.Error(d.p.newError(start, end, "cdata", locale.ErrIsEmpty, field)) } if n.Content != nil && canNotEmpty(n.Content) { - d.p.Error(d.p.NewError(start, end, "content", locale.ErrIsEmpty, field)) + d.p.Error(d.p.newError(start, end, "content", locale.ErrIsEmpty, field)) } } @@ -222,7 +222,7 @@ func (d *decoder) decodeElements(n *node.Node) (end *EndElement, ok bool) { t, loc, err := d.p.Token() if errors.Is(err, io.EOF) { // 应该只有 EndElement 才能返回,否则就不完整的 XML - d.p.Error(d.p.NewError(d.p.Current().Position, d.p.Current().Position, "", locale.ErrNotFoundEndTag)) + d.p.Error(d.p.newError(d.p.Current().Position, d.p.Current().Position, "", locale.ErrNotFoundEndTag)) return nil, false } else if err != nil { d.p.Error(err) @@ -234,7 +234,7 @@ func (d *decoder) decodeElements(n *node.Node) (end *EndElement, ok bool) { if (elem.Name.Local.Value == n.Value.Name) && (elem.Name.Prefix.Value == d.prefix) { return elem, true } - d.p.Error(d.p.NewError(elem.Location.Range.Start, elem.Location.Range.End, n.Value.Name, locale.ErrNotFoundEndTag)) + d.p.Error(d.p.newError(elem.Location.Range.Start, elem.Location.Range.End, n.Value.Name, locale.ErrNotFoundEndTag)) return nil, false case *CData: if n.CData != nil { @@ -252,7 +252,7 @@ func (d *decoder) decodeElements(n *node.Node) (end *EndElement, ok bool) { return nil, false } - e := d.p.NewError(elem.Location.Range.Start, d.p.Current().Position, elem.Name.String(), locale.ErrInvalidTag). + e := d.p.newError(elem.Location.Range.Start, d.p.Current().Position, elem.Name.String(), locale.ErrInvalidTag). AddTypes(core.ErrorTypeUnused) d.p.Warning(e) break // 忽略不存在的子元素 diff --git a/internal/xmlenc/parser.go b/internal/xmlenc/parser.go index 0a57ddad..3522f8ae 100644 --- a/internal/xmlenc/parser.go +++ b/internal/xmlenc/parser.go @@ -103,7 +103,7 @@ func (p *Parser) parseComment(pos lexer.Position) (*Comment, core.Location, erro data, found := p.DelimString("-->", false) if !found { - return nil, core.Location{}, p.NewError(p.Current().Position, p.Current().Position, " 三个字符 @@ -127,7 +127,7 @@ func (p *Parser) parseStartElement(pos lexer.Position) (*StartElement, core.Loca start := p.Current() name, found := p.DelimFunc(func(r rune) bool { return unicode.IsSpace(r) || r == '/' || r == '>' }, false) if !found || len(name) == 0 { - return nil, core.Location{}, p.NewError(p.Current().Position, p.Current().Position, "", locale.ErrInvalidXML) + return nil, core.Location{}, p.newError(p.Current().Position, p.Current().Position, "", locale.ErrInvalidXML) } elem := &StartElement{ @@ -152,7 +152,7 @@ func (p *Parser) parseStartElement(pos lexer.Position) (*StartElement, core.Loca return elem, elem.Location, nil } - return nil, core.Location{}, p.NewError(p.Current().Position, p.Current().Position, string(name), locale.ErrNotFoundEndTag) + return nil, core.Location{}, p.newError(p.Current().Position, p.Current().Position, string(name), locale.ErrNotFoundEndTag) } func parseName(name []byte, uri core.URI, start, end core.Position) Name { @@ -188,7 +188,7 @@ func (p *Parser) parseEndElement(pos lexer.Position) (*EndElement, core.Location name, found := p.Delim('>', false) if !found || len(name) == 0 { - return nil, core.Location{}, p.NewError(p.Current().Position, p.Current().Position, "", locale.ErrInvalidXML) + return nil, core.Location{}, p.newError(p.Current().Position, p.Current().Position, "", locale.ErrInvalidXML) } end := p.Current() p.Next(1) // 去掉 > 符号 @@ -211,7 +211,7 @@ func (p *Parser) parseCData(pos lexer.Position) (*CData, core.Location, error) { for { v, found := p.DelimString(cdataEnd, false) if !found { - return nil, core.Location{}, p.NewError(pos.Position, p.Current().Position, cdataStart, locale.ErrNotFoundEndTag) + return nil, core.Location{}, p.newError(pos.Position, p.Current().Position, cdataStart, locale.ErrNotFoundEndTag) } value = append(value, v...) @@ -291,7 +291,7 @@ func (p *Parser) parseCData(pos lexer.Position) (*CData, core.Location, error) { func (p *Parser) parseInstruction(pos lexer.Position) (*Instruction, core.Location, error) { name, nameRange := p.getName() if len(name) == 0 { - return nil, core.Location{}, p.NewError(p.Current().Position, p.Current().Position, "", locale.ErrInvalidXML) + return nil, core.Location{}, p.newError(p.Current().Position, p.Current().Position, "", locale.ErrInvalidXML) } elem := &Instruction{ Location: core.Location{URI: p.Location.URI}, @@ -316,7 +316,7 @@ func (p *Parser) parseInstruction(pos lexer.Position) (*Instruction, core.Locati return elem, elem.Location, nil } - return nil, core.Location{}, p.NewError(p.Current().Position, p.Current().Position, "