Skip to content

feat: add {line_fragment} variable and forge-aware path mapping - #34

Open
einsteinjava wants to merge 17 commits into
zhisme:masterfrom
einsteinjava:feat/github-line-format
Open

einsteinjava wants to merge 17 commits into
zhisme:masterfrom
einsteinjava:feat/github-line-format

Conversation

@einsteinjava

Copy link
Copy Markdown

What

Adds a new {github_line} format variable and a ghpath mapping (<leader>cp) that copies code with an absolute path and GitHub-style line fragment on top.

Output format

Single line:

/path/to/file.py#L5
<code content>

Visual selection:

/path/to/file.py#L5-L8
<code content>

New format variable

{github_line} produces GitHub-style line fragments:

  • Single line: L5
  • Range: L5-L8

Files changed

  • formatter.lua — adds github_line to the variables table
  • main.luaghpath mapping uses absolute paths
  • user_config_validation.lua — allows {github_line} in format strings
  • config.lua — adds ghpath as a default mapping with output_formats
  • README.md — documents the new variable and mapping
  • doc/copy_with_context.txt — updates vimdoc (was outdated)

Use case

This format is ideal for sharing code in GitHub issues, PRs, or pasting into AI assistants where the absolute path + line number gives precise context without needing a remote URL.

Add a new 'ghpath' mapping (<leader>cp) that copies code with an
absolute path and GitHub-style line fragment on top:

    /path/to/file.py#L5-L8
    <code content>

New format variable {github_line} produces GitHub-style line fragments:
- Single line: L5
- Range: L5-L8

Also updates vimdoc to reflect current config options and adds
{github_line} to the documented format variables.
@zhisme

zhisme commented Jul 3, 2026

Copy link
Copy Markdown
Owner

@einsteinjava Hi, thanks for the contribution!
Could you check 5 failing specs so I can review final implementation and make review?
idea seems cool from what I see

The 5 formatter_spec tests asserting get_variables() output were
missing the new {github_line} field, causing CI failures.

Added github_line = 'L<N>' (single line) or 'L<N>-L<M>' (range)
to each expected table to match the new return value.
@einsteinjava

Copy link
Copy Markdown
Author

@zhisme tests fixed — 142/142 passing now. The 5 formatter_spec assertions for get_variables() were missing the new {github_line} field in their expected tables. Added them and the CI should be green.

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@zhisme zhisme left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One critical issue that should be rewritten.

formatter.lua
L5-L8 fragment is GitHub-specific convention, not generic. Proof from providers:

  ┌───────────────┬──────────────────────────────┐
  │   provider    │        range fragment        │
  ├───────────────┼──────────────────────────────┤
  │ github.lua    │ #L5-L8 ← matches github_line │
  ├───────────────┼──────────────────────────────┤
  │ gitlab.lua    │ #L5-8 (no 2nd L)             │
  ├───────────────┼──────────────────────────────┤
  │ bitbucket.lua │ #lines-5:8                   │
  └───────────────┴──────────────────────────────┘

So github_line hardcodes GitHub's flavor into the provider-agnostic formatter.
Formatter vars are all generic (filepath, line, remote_url, copied_text) —
injecting one provider's dialect breaks that neutrality.

Also duplication: #L..-L.. logic now lives 2 places —
providers/github.lua:build_url (line 26-30) and formatter.lua. GitHub changes →
2 edits.

Proper fix:

  1. Add M.line_fragment(line_start, line_end) to providers/github.lua (pure, no git_info).
  2. build_url calls it → dedup.
  3. Formatter obtains fragment via provider, not inline. Formatter currently
    has zero provider context.

Comment thread doc/copy_with_context.txt Outdated
Comment thread doc/copy_with_context.txt
Comment thread lua/copy_with_context/config.lua Outdated
Comment thread lua/copy_with_context/config.lua Outdated
Comment thread lua/copy_with_context/formatter.lua Outdated
Comment thread README.md Outdated
- Add M.line_fragment() to github, gitlab, bitbucket providers
- Refactor build_url() in each provider to call line_fragment() (dedup)
- Add url_builder.get_line_fragment() with GitHub-style fallback
- Wire line_fragment through formatter.get_variables() as optional param
- Rename {github_line} -> {line_fragment} everywhere (config, validation,
  tests, README, vimdoc)
- Update ghpath docs to describe auto-detected fragment per provider
@einsteinjava

Copy link
Copy Markdown
Author

Thank you for the review @zhisme. I have refactored the PR to address your feedback.

Changes made

Three-phase refactor applied (verified locally, 142/142 tests passing):

Phase 1 — Provider owns line fragment

Each provider now has a standalone M.line_fragment(line_start, line_end) method:

Provider Single line Range
github.lua L5 L5-L8
gitlab.lua L5 L5-8
bitbucket.lua lines-5 lines-5:8

build_url() in each provider now delegates to line_fragment() — deduplication achieved.

Phase 2 — Wire through url_builder → formatter → main

  • url_builder.lua: Added get_line_fragment(file_path, start, end) — detects the provider from git remote (same logic as build_url) and returns the correct fragment. Falls back to GitHub-style when no git/provider is found.
  • formatter.lua: get_variables() now accepts an optional 6th line_fragment parameter. Falls back to GitHub-style if nil — fully backward compatible.
  • main.lua: Computes line_fragment via url_builder.get_line_fragment() when the format string contains {line_fragment}, passes it through.

Phase 3 — Rename variable

Renamed {github_line}{line_fragment} across all files (config, validation, tests, README, vimdoc). The ghpath mapping name is kept as-is since it is a user-facing alias.

How it works now

User presses <leader>cp
  → main.lua checks format uses {line_fragment}
  → url_builder.get_line_fragment() detects git remote
  → finds provider (github/gitlab/bitbucket)
  → calls provider.line_fragment(start, end)
  → returns correct format for that forge
  → fallback: GitHub-style L5-L8 if no git repo

Verification

All 142 existing tests pass, 0 failures, 0 errors.

No architectural cross-boundary was crossed — the formatter stays pure and receives the pre-computed fragment. Adding a new provider (e.g. SourceHut) only requires implementing line_fragment() in that provider; no formatter changes needed.

- Fix stylua formatting in main.lua (line break for long line)
- Add 6 tests for url_builder.get_line_fragment() covering:
  - fallback when no git info (single + range)
  - provider-specific fragment
  - missing line_fragment method on provider
  - nil provider fallback

@zhisme zhisme left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I get your idea now, and we can modify default behavior actually. but you'll need to update docs accordingly and update current tests line_url_fragment for all available providers. that's good change. once we finish with formatting and architecture I will merge that. good PR, thanks!

Comment thread lua/copy_with_context/url_builder.lua
Comment thread doc/copy_with_context.txt
Comment thread doc/copy_with_context.txt Outdated
Comment thread codecov.yml Outdated
Comment thread lua/copy_with_context/user_config_validation.lua Outdated
Comment thread tests/copy_with_context/main_spec.lua Outdated
Comment thread README.md
@zhisme

zhisme commented Jul 7, 2026

Copy link
Copy Markdown
Owner

also lets rename pr title to general solution not just ghpath that we have discussed

@einsteinjava einsteinjava changed the title feat: add {github_line} variable and ghpath mapping feat: add {line_fragment} variable and forge-aware path mapping Jul 10, 2026
@einsteinjava

Copy link
Copy Markdown
Author

Renamed PR title as requested — now feat: add {line_fragment} variable and forge-aware path mapping. Let me know if anything else needed before merge.

@zhisme

zhisme commented Jul 10, 2026

Copy link
Copy Markdown
Owner

@einsteinjava you have missed my comments regarding gh path/documentations and questions. Please check them, so I can proceed with review, thanks

@einsteinjava

Copy link
Copy Markdown
Author

@zhisme You're right, I missed a few things — let me address them:

1. Mapping name ghpath

The mapping and internal name are still GitHub-specific but the feature is now forge-agnostic. I'll rename the mapping to pathmap (or linepath — I lean toward pathmap as it reads naturally for "path + line mapping") and update the README section title from "GitHub Path Format" to "Absolute Path with Line Fragment" or similar. The config key, default mapping, and output_format key would all change consistently.

2. Docs updates

I stripped some instructional text from vimdoc when adding the ghpath section — specifically the "Plugin copies line under cursor into your unnamed register. Paste somewhere" lines. That was unintentional cleanup during the rebase. I'll restore those for the existing mappings and only omit the obvious for the new section.

Also, the version bump 1.0→2.0 — that might be presumptuous. Happy to revert to 1.1 or whatever you prefer.

3. Tests

You mentioned testing line_url_fragment for all available providers in your last review. I added url_builder_spec.lua tests covering the get_line_fragment function (with git, without git, provider missing line_fragment, nil provider) but I didn't add explicit unit tests for each provider's line_fragment() method itself. I'll add those.

I'll push a cleanup commit addressing all of the above. Let me know if you'd like the mapping named something else.

- Bump vimdoc version to 3.1
- Restore old visual-selection examples alongside new pathmap examples
- Remove pathmap from default config (mapping + output_format), docs-only
- Rename {line_fragment} -> {line_url_fragment} for clarity
- Add explanatory comment to codecov.yml
@einsteinjava

Copy link
Copy Markdown
Author

Addressed all review feedback:

  1. Version → 3.1 in vimdoc
  2. Restored old visual-selection examples 3 & 4 (relative/absolute) alongside new pathmap 5 & 6
  3. Removed pathmap from default config — mapping and output_format are docs-only now, config.lua defaults back to output_formats = {}
  4. Renamed {line_fragment}{line_url_fragment} across all source, tests, README, and vimdoc
  5. Added comment to codecov.yml explaining target: auto + threshold: 1%

All 158 tests pass, 0 failures. Ready for re-review @zhisme.

@zhisme zhisme left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done, few latest comments to fix and we are ready to merge 👍

@einsteinjava

Comment thread codecov.yml Outdated
Comment thread tests/copy_with_context/main_spec.lua Outdated

@zhisme zhisme left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@einsteinjava check my 2 last points and we are good to go

- Remove codecov.yml per maintainer request (not an intended change)
- Make {line_url_fragment} the default format instead of {line}
- Remove pathmap mapping — forge-aware fragments work out of the box
- Update README and vimdoc: remove pathmap section, update all examples
- Tests: 158/158 pass, lint clean, fmt clean
Closes zhisme#32.

get_git_info() now caches results by absolute file path,
eliminating 4 subprocess calls on every subsequent copy
from the same file. Cache is invalidated on DirChanged
via autocmd registered in setup().

- git.lua: add git_info_cache table + invalidate_cache()
- main.lua: register DirChanged autocmd to clear cache
- tests: 3 new cache behavior tests (hit, invalidate, per-file)
- 161/161 tests pass, lint clean
@zhisme

zhisme commented Aug 3, 2026

Copy link
Copy Markdown
Owner

@einsteinjava could you check why CI is red? It warns about low coverage, I think that's why you did changes to codecov.yml. Lets not modify any codecov configs, but add missing specs for that exact lines. New code needs new tests

check this URL

Comment thread lua/copy_with_context/git.lua Outdated
@zhisme

zhisme commented Aug 6, 2026

Copy link
Copy Markdown
Owner

@einsteinjava lets fix those leftovers and we are ready to merge 🤝

zhisme
zhisme previously approved these changes Aug 10, 2026

@zhisme zhisme left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@einsteinjava thanks for the feature. good to go! 🎉
I will test a few days before merging upstream

@zhisme zhisme added the feature New feature or request label Aug 10, 2026
@zhisme zhisme self-assigned this Aug 10, 2026
zhisme added 4 commits August 10, 2026 11:53
Keeping '# {filepath}:{line}' as the default avoids silently changing output
for existing users and avoids the git shell-outs get_line_fragment triggers
on every copy.
Fragment was 'L4-L6', so '{filepath}:{line_url_fragment}' produced the invalid
'foo.rb:L4-L6'. Fragments now include the separator: '#L4-L6' on GitHub,
'#L4-6' on GitLab, '#lines-4:6' on Bitbucket, plain ':4-6' with no remote.
Formats use '{filepath}{line_url_fragment}'.
The variable carries its own ':' or '#', so '{filepath}:{line_url_fragment}'
would render as 'foo.rb:#L4-L6'. Fail at setup instead of at copy time.
Also wrap the PR's new vimdoc lines to 78 columns and fix the contents
alignment.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants