feat(navigation): gf navigation for file references and include expressions#18
Merged
StanAngeloff merged 5 commits intodevelopfrom Mar 12, 2026
Merged
feat(navigation): gf navigation for file references and include expressions#18StanAngeloff merged 5 commits intodevelopfrom
StanAngeloff merged 5 commits intodevelopfrom
Conversation
b87b8d4 to
b378946
Compare
…xpressions
Enable gf / <C-w>f navigation in .chat buffers. Cursor on @./file or
{{ include('path') }} resolves the target path using the expression
evaluator with a capturing include() stub — no I/O, just path resolution.
Infrastructure changes:
- Add end_col to ast.Position and compute it in the parser for expression
and file-reference segments
- Add find_segment_at_position() to ast/query with a fallback pattern
that handles column-less text segments alongside column-aware expressions
- Wire includeexpr buffer option via v:lua in apply_chat_buffer_settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion Instruments resolve_include_path with trace-level logging for cursor position, segment lookup, and include() capture flow. Debug-level logs for the resolved path and eval failures. Helps diagnose gf navigation issues without stepping through code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When an include result is assigned to a frontmatter variable and
referenced in an expression (e.g., {{ mod }} where mod = include(file)),
gf navigation now resolves the path by inspecting the eval result for
a SOURCE_PATH symbol rather than relying solely on capturing the
include() call.
Introduces flemma.symbols — centralized well-known table keys (analogous
to JavaScript's Symbol) for cross-module metadata tagging. Emittable
include parts are tagged with symbols.SOURCE_PATH at construction time,
making the originating file path available to any downstream consumer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Navigation was overriding env.include with a capturing stub that duplicated path resolution logic and crashed on nil arguments. Instead, evaluate the expression with the real include() and check the result for symbols.SOURCE_PATH — simpler, correct, and no behaviour divergence from normal evaluation. Also adds a type guard to eval.lua's include() so passing a non-string argument produces a clear diagnostic instead of crashing on :sub(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add {, }, (, ) to buffer-local isfname so Neovim's gf extracts a
candidate covering the full {{ ... }} expression at any cursor
position — without these, gf on delimiters or whitespace within
the expression would not trigger includeexpr.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
b378946 to
d0a27df
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
gf/<C-w>fnavigation support to.chatbuffers. When the cursor is on an@./filereference or a{{ include('path') }}expression, pressinggfopens the referenced file, and<C-w>fopens it in a split.@./filereferences to absolute paths via the existing include() machinery{{ include('path') }}expressions by evaluating with a capturing stub{{ include(my_var) }})v:fnamebehavior when cursor is not on an include expressionArchitecture
New infrastructure (shared with future LSP hover plan)
end_colonflemma.ast.Position— The AST position type previously hadstart_colbut notend_col. Both expression segments ({{ expr }}) and file reference segments (@./file) now track their ending column. For file references,end_colexcludes trailing punctuation (e.g. the comma in@./file.txt, more text). This is needed for precise cursor-to-segment matching.find_segment_at_position(doc, lnum, col)inast/query.lua— A new query function that finds the segment (and its parent message) at a given 1-indexed cursor position. The algorithm handles mixed segment types on the same line: column-aware segments (expressions, file refs) get precise matching, while column-less segments (plain text) serve as fallbacks when no column-specific match is found. This prevents text segments from shadowing expression segments on the same line.Navigation module additions
resolve_include_path(bufnr)— The core resolution function. It:find_segment_at_positioninclude()stub that records the resolved path without performing any I/O (no file reads, no MIME detection, no circular-include checks)pcall(eval.eval_expression, ...)— if it callsinclude(), we capture the path; if it doesn't (e.g.{{ 2 + 2 }}), we return nilresolve_include_path_expr()— A thin wrapper called by Vim'sincludeexproption. Returns the resolved path or falls back tovim.v.fname.Wiring
apply_chat_buffer_settingsinui/init.luasetsincludeexprtov:lua.require("flemma.navigation").resolve_include_path_expr(). Therequirehappens lazily atgf-press time (not at buffer setup), sonavigation.luais not added toui/init.lua's top-level requires.Logging
Debug and trace logging throughout
resolve_include_pathcovering: cursor position, segment lookup, expression code, frontmatter diagnostics, include() capture details, eval failures, and resolved path. Follows the project convention of"navigation: ..."prefixed messages.Files changed
lua/flemma/ast/nodes.luaend_col? integerto Position typelua/flemma/ast/query.luafind_segment_at_position()with fallback patternlua/flemma/parser.luaend_colfor expression and file-reference segmentslua/flemma/navigation.luaresolve_include_path,resolve_include_path_expr, logginglua/flemma/ui/init.luaincludeexprbuffer optiontests/flemma/ast_spec.luaend_colandfind_segment_at_positiontests/flemma/includeexpr_spec.luatests/fixtures/include_target.txt.changeset/includeexpr-navigation.mdTest plan
make qapasses (luacheck, LuaLS types, inline-requires lint, full test suite).chatfile, place cursor on@./some-file.txt, pressgf— file opens{{ include('./some-file.txt') }}, pressgf— file opensgf— default Vim behavior (no error)include(var),gfresolves correctly🤖 Generated with Claude Code