Skip to content

Proposal: provide a way to detect NGINX context based on line number #142

Open
@ryepup

Description

@ryepup

Is your feature request related to a problem? Please describe

I want a better way to find the NGINX context based on line number.

I was attempting to write a language server for NGINX, using nginx-go-crossplane to parse files and then cross-reference with nginx-directive-reference to drive IDE features like autocomplete and accurate documentation.

The problem I have is NGINX directives often re-use a name; if my language server wants to show a tooltip and when the user hovers over a listen directive, I don't have a good way to tell if they mean HTTP listen, stream listen or mail listen.

Sometimes it's possible to walk the Config.Parsed tree to detect parent context.

Sometimes the user might ask for autocomplete suggestions on a blank line, and all I have to work with is a line number. The Payload does not contain enough information to determine NGINX context if there's not a directive on that line.

Describe the solution you'd like

Parse keeps a record of the current NGINX context (blockCtx), I'd like that information to escape.

 type Directive struct {
 	Directive string     `json:"directive"`
 	Line      int        `json:"line"`
+	// EndLine is the line number with the } that ends this block directive. Will be nil for directives that are not blocks.
+	EndLine      *int        `json:"endLine,omitempty"`
 	Args      []string   `json:"args"`

I think this can be captured with some non-trivial refactoring around parser.Parse to return the line number with recursive calls.

Describe alternatives you've considered

I tried to recursively walk Config.Parsed, keeping track of the last directive where d.Line < x && len(d.Block) > 0, but this fails to detect when a context ends.

From F5 internal prototype:

export function findClosestDirective(p: Payload, line: number): Directive | undefined {
    let possibleParent: Directive | undefined = undefined;
    for (const d of allDirectives(p)) { // recursively walks the directive tree and generates a flat list
        if (d.line === line) {
            return d
        }
        if (d.line < line && d.block) {
            possibleParent = d
        }
        if (d.line > line) {
            return possibleParent
        }
    }
    return possibleParent
}
http {
  server { # ✅ I can walk the Config.Parsed tree to see this server is in an http
    # ✅  on a blank the last seen context on this line is "server" 
  }
  # ❌ on a blank the last seen context on this line is "server", as the } is not represented in the payload
}

I considering requesting something easier to implement like:

 type Directive struct {
 	Directive string     `json:"directive"`
 	Line      int        `json:"line"`
+	// Context is the stack of NGINX contexts that contain this directive.
+	Context      []string        `json:"context,omitempty"`
 	Args      []string   `json:"args"`

But doesn't doesn't solve the use case where all I have is a line number.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions