Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions docs/configuration.org
Original file line number Diff line number Diff line change
Expand Up @@ -2310,6 +2310,7 @@ these types:
- Planning keywords (=DEADLINE=, =SCHEDULED=, =CLOSED=)
- Orgfile special keywords (=#+TITLE=, =#+BEGIN_SRC=, =#+ARCHIVE=, etc.)
- Hyperlinks (=* - headlines=, =# - headlines with CUSTOM_ID property=, =headlines matching title=)
- Citation keys (inside =[cite:...]= blocks after =@=)

Autocompletion is context aware, which means that for example
tags autocompletion will kick in only when cursor is at the end of
Expand Down Expand Up @@ -2539,6 +2540,162 @@ require('orgmode').setup({
})
#+end_src

*** Citations
:PROPERTIES:
:CUSTOM_ID: citations
:END:
Orgmode supports [[https://orgmode.org/manual/Citation-handling.html][Org 9.5 citations]] using the =[cite:@key]= syntax.
For example:

#+begin_src org
See [cite:@smith2020] for details, or [cite/t:@jones2021;@doe2022] for a
textual citation.
#+end_src

The citation key under the cursor can be followed to its bibliography entry
using the same =org_open_at_point= mapping (=<Leader>oo= by default) that is
used for hyperlinks.

Citation key autocompletion is provided via the standard =omnifunc= (triggered
by =<C-x><C-o>= in insert mode). Completion is active inside any =[cite:...]=
or =[cite/style:...]=, after the =@= character.

**** Bibliography files
:PROPERTIES:
:CUSTOM_ID: citations-bibliography-files
:END:
The built-in BibTeX source reads citation keys from =.bib= files. Two
discovery mechanisms are available (both can be used simultaneously):

***** Global bibliography
:PROPERTIES:
:CUSTOM_ID: org_cite_global_bibliography
:END:
Set =citations.org_cite_global_bibliography= to a path or list of paths that
are always searched, regardless of which file is open:

#+begin_src lua
require('orgmode').setup({
citations = {
org_cite_global_bibliography = {
'~/references/global.bib',
'~/references/books.bib',
},
},
})
#+end_src

A single string is also accepted:

#+begin_src lua
require('orgmode').setup({
citations = {
org_cite_global_bibliography = '~/references/global.bib',
},
})
#+end_src

***** File-local bibliography
:PROPERTIES:
:CUSTOM_ID: citations-file-local-bibliography
:END:
Any =.org= file can declare its own bibliography with one or more
=#+bibliography:= directives. Paths are resolved relative to the org file:

#+begin_src org
#+bibliography: refs.bib
#+bibliography: /absolute/path/to/extra.bib

* Introduction
See [cite:@smith2020] for background.
#+end_src

**** Custom citation sources
:PROPERTIES:
:CUSTOM_ID: citations-custom-sources
:END:
Additional citation sources can be registered via =citations.sources=. Each
source is a table (or object) that implements the =OrgCitationSource=
interface:

- =get_name()= (required) — return a unique name string for the source.
- =get_items()= (required) — return a list of =OrgCitationItem= tables.
Each item must have a =key= field, and may optionally have =label= and
=description= fields used in completion menus.
- =follow(key)= (optional) — navigate to the entry for =key=; return =true=
if handled, =false= to fall through to the next source.

#+begin_src lua
require('orgmode').setup({
citations = {
sources = {
{
get_name = function() return 'my_source' end,
get_items = function()
return {
{ key = 'smith2020', description = 'Smith et al. 2020' },
{ key = 'jones2021' },
}
end,
follow = function(self, key)
vim.notify('Citation: ' .. key)
return true
end,
},
},
},
})
#+end_src

***** Example: Zotero Local API
:PROPERTIES:
:CUSTOM_ID: citations-zotero-local-api
:END:
[[https://www.zotero.org][Zotero]] exposes a local HTTP API on =http://localhost:23119= (requires the
Zotero desktop application to be running). The example below defines a
custom source that queries the local API for all library items and exposes
their citation keys for completion.
#+begin_src lua
local ZoteroSource = {}

function ZoteroSource:get_name()
return 'zotero'
end

function ZoteroSource:get_items()
local curl = require('plenary.curl')
local res = curl.get {
url = 'http://localhost:23119/api/users/0/items?format=json',
accept = 'application/json',
}
if res.status == 200 then
local data = vim.json.decode(res.body or '') or {}
local items = {}
for _, entry in ipairs(data) do
local d = entry.data or {}
if d.citationKey then
local creators = d.creators or {}
local author = (creators[1] or {}).lastName or ''
local year = (d.date or ''):match('%d%d%d%d') or ''
table.insert(items, {
key = d.citationKey,
description = author .. year .. ' ' .. (d.title or ''),
})
end
end
return items
else
return {}
end
end

require('orgmode').setup({
citations = {
sources = { ZoteroSource },
},
})
#+end_src

*** Notifications
:PROPERTIES:
:CUSTOM_ID: notifications
Expand Down
2 changes: 2 additions & 0 deletions lua/orgmode/colors/highlights.lua
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ function M.link_highlights()
['@org.latex'] = '@markup.math',
['@org.latex_env'] = '@markup.environment',
['@org.footnote'] = '@markup.link.url',
['@org.citation'] = '@markup.link',
['@org.citation.reference'] = '@markup.link.url',
-- Other
['@org.table.delimiter'] = '@punctuation.special',
['@org.table.heading'] = '@markup.heading',
Expand Down
4 changes: 4 additions & 0 deletions lua/orgmode/config/defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ local DefaultConfig = {
hyperlinks = {
sources = {},
},
citations = {
sources = {},
org_cite_global_bibliography = {},
},
mappings = {
disable_all = false,
org_return_uses_meta_return = false,
Expand Down
7 changes: 6 additions & 1 deletion lua/orgmode/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ local auto_instance_keys = {
notifications = true,
completion = true,
links = true,
citations = true,
}

---@class Org
Expand All @@ -27,6 +28,7 @@ local auto_instance_keys = {
---@field org_mappings OrgMappings
---@field notifications OrgNotifications
---@field links OrgLinks
---@field citations OrgCitations
local Org = {}
setmetatable(Org, {
__index = function(tbl, key)
Expand Down Expand Up @@ -60,6 +62,7 @@ function Org:init()
})
:load_sync(true, 20000)
self.links = require('orgmode.org.links'):new({ files = self.files })
self.citations = require('orgmode.org.citations'):new({ files = self.files })
self.agenda = require('orgmode.agenda'):new({
files = self.files,
highlighter = self.highlighter,
Expand All @@ -68,12 +71,14 @@ function Org:init()
self.capture = require('orgmode.capture'):new({
files = self.files,
})
self.completion = require('orgmode.org.autocompletion'):new({ files = self.files, links = self.links })
self.completion =
require('orgmode.org.autocompletion'):new({ files = self.files, links = self.links, citations = self.citations })
self.org_mappings = require('orgmode.org.mappings'):new({
capture = self.capture,
agenda = self.agenda,
files = self.files,
links = self.links,
citations = self.citations,
completion = self.completion,
})
self.clock = require('orgmode.clock'):new({
Expand Down
5 changes: 4 additions & 1 deletion lua/orgmode/org/autocompletion/init.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---@class OrgCompletion
---@field files OrgFiles
---@field links OrgLinks
---@field citations OrgCitations
---@field private sources OrgCompletionSource[]
---@field private sources_by_name table<string, OrgCompletionSource>
---@field private fuzzy_match? boolean does completeopt has fuzzy option
Expand All @@ -10,11 +11,12 @@ local OrgCompletion = {
}
OrgCompletion.__index = OrgCompletion

---@param opts { files: OrgFiles, links: OrgLinks }
---@param opts { files: OrgFiles, links: OrgLinks, citations: OrgCitations }
function OrgCompletion:new(opts)
local this = setmetatable({
files = opts.files,
links = opts.links,
citations = opts.citations,
sources = {},
sources_by_name = {},
fuzzy_match = vim.tbl_contains(vim.opt_local.completeopt:get(), 'fuzzy'),
Expand All @@ -31,6 +33,7 @@ function OrgCompletion:setup_builtin_sources()
self:add_source(require('orgmode.org.autocompletion.sources.directives'):new())
self:add_source(require('orgmode.org.autocompletion.sources.properties'):new({ completion = self }))
self:add_source(require('orgmode.org.autocompletion.sources.hyperlinks'):new({ completion = self }))
self:add_source(require('orgmode.org.autocompletion.sources.citations'):new({ completion = self }))
end

---@param source OrgCompletionSource
Expand Down
39 changes: 39 additions & 0 deletions lua/orgmode/org/autocompletion/sources/citations.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---@class OrgCompletionCitations:OrgCompletionSource
---@field completion OrgCompletion
---@field private pattern vim.regex
local OrgCompletionCitations = {}
OrgCompletionCitations.__index = OrgCompletionCitations

---@param opts { completion: OrgCompletion }
function OrgCompletionCitations:new(opts)
return setmetatable({
completion = opts.completion,
pattern = vim.regex([=[\[cite[/:][^\]]*@\zs[^ \]]*$]=]),
}, OrgCompletionCitations)
end

---@return string
function OrgCompletionCitations:get_name()
return 'citations'
end

---@param context OrgCompletionContext
---@return number | nil
function OrgCompletionCitations:get_start(context)
return self.pattern:match_str(context.line)
end

---@param _ OrgCompletionContext
---@return string[]
function OrgCompletionCitations:get_results(_)
local citations = self.completion.citations
if not citations then
return {}
end
local items = citations:get_items()
return vim.tbl_map(function(item)
return item.key
end, items)
end

return OrgCompletionCitations
1 change: 1 addition & 0 deletions lua/orgmode/org/autocompletion/sources/directives.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function OrgCompletionDirectives:get_results(_)
'#+begin_example',
'#+end_src',
'#+end_example',
'#+bibliography',
}
end

Expand Down
11 changes: 11 additions & 0 deletions lua/orgmode/org/citations/_meta.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---@meta

---@class OrgCitationItem
---@field key string The citation key (e.g. "smith2020")
---@field label? string Optional display label used in completion menus
---@field description? string Optional human-readable description (author, title, year, etc.)

---@class OrgCitationSource
---@field get_name fun(self: OrgCitationSource): string Return the unique name of this source
---@field get_items fun(self: OrgCitationSource): OrgCitationItem[] Return all citation items
---@field follow? fun(self: OrgCitationSource, key: string): boolean Navigate to the entry for the given key; return true if handled
Loading
Loading