Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add template feature for new pages #83

Merged
merged 10 commits into from
Jul 1, 2021
Merged

Add template feature for new pages #83

merged 10 commits into from
Jul 1, 2021

Conversation

lervag
Copy link
Owner

@lervag lervag commented Jun 21, 2020

Tasks:

  • Write specification in the docs.
  • Implement feature according to the specification.
  • Write tests.

See also #46 for more context and discussion.

Feedback wanted: @Corey-Keller, @gauteh, @jabirali

Note: I think the specs are quite good now. I think the most important thing now is to agree on formats and structure, then we can add more interpolation features later, if we deem it necessary.

@funguscolander
Copy link
Contributor

funguscolander commented Jun 23, 2021

I see this hasn't really had much progress over the past year but for what it's worth, the specification describes most of what I would use it for.

I use two different styles of notes depending on if they're named "index" or not. This could be implemented by checking for the name of the file against a list of templates of the same filename, and failing that it could then fallback to a generic template. Something like:

let g:wiki_specific_template_files = [
      \ '.index.md;',
      \ '/home/user/templates/index.md',
      \ '.categories.md;',
      \ '/home/user/templates/categories.md',
      \]

let g:wiki_generic_template_files = [
      \ '.mytemplate.md;',
      \ '/home/user/templates/fallback.md',
      \]

Where the filename would compare against items in g:wiki_specific_template_files, and then look for the file if there is a match or moves onto looking for the files in g:wiki_generic_template_files if there is not a match. I understand that this may be a niche usecase though so I won't be distraught if it isn't implemented, this specification is good.

Apologies for not offering to help, I have made attempts to figure this out already but I don't know vimscript well enough.

@lervag
Copy link
Owner Author

lervag commented Jun 24, 2021

I see this hasn't really had much progress over the past year but for what it's worth, the specification describes most of what I would use it for.

Thanks! That's worth a bunch. The lack of progress is mostly due to lack of interest. If I had a strong personal interest it would of course already have been implemented. But with external interest and input I would not mind picking up the threads and finishing this.

Apologies for not offering to help, I have made attempts to figure this out already but I don't know vimscript well enough.

No problem! From my perspective, having a discussion to settle the discussion is in itself an important contribution!

I use two different styles of notes depending on if they're named "index" or not. ...

You raise an interesting idea - that it should be possible to apply different templates depending on e.g. the target file name. How about the following refined idea?

let g:wiki_templates = [ { template_dict }... ]

That is, we define a list of templates whose "behaviour" is specified with a dictionary. The template feature would be implemented similar to this:

" new file is created first, then:
for template in g:wiki_templates
  if applies(template, context)
    call apply(template, context)
    break
  endif
endfor

Here the applies and apply functions would be implemented in wiki.vim and would rely on the attributes of the template_dict. E.g.:

  • template_dict.match_re: applies is true if filename matches regex.
  • template_dict.match_function: applies if match_function returns true with the context as argument (this is only applicable if match_re is not defined).
  • template_dict.source_file: use a template file as the template source (as specified in the current specification).
  • template_dict.source_function: use a custom function as the template source with the context as argument.

What do you think?

@funguscolander
Copy link
Contributor

funguscolander commented Jun 25, 2021

This seems like a better method, both for identifying the template to be used and for the templates themselves, as source_function has the potential to be more dynamic than a .md or .rmd file. match_function could also be used to solve the issue of identifying the containing directory in the following suggestion:

A suggestion based on the discussion of the pagetitle definition in #46, where the name of the file may not be the desired title for the document; can there be an additional context element and variable called {directory} or {containing_folder}, and the equivalent for the origin called {last_directory} or {last_containingfolder}? This would allow different templates based on the directory of the wiki containing the file, as well as allow the directory name to be used as the title as you suggested in that post.

An alternative for the title might be to use the display text of the link used to create the file (i.e. [THIS TEXT](...) for markdown and [[...|THIS TEXT]] for wiki links). I don't know if this approach is any better, but it has the disadvantage(?) quirk of being defined by the display text of the first link used to create the file, which isn't nearly as strict as using the name of the directory.

template_dict.match_function: applies if match_function returns true with the context as argument (this is only applicable if match_re is not defined).

I am not sure what you mean by "this is only applicable if match_re is not defined." In the example code under *g:wiki_templates*, match_function is used immediately after match_re:

    let g:wiki_templates = [
          \ { 'match_re': 'index\.md',
          \   'source_filename': '/home/user/templates/index.md'},
          \ { 'match_re': 'foo\.md',
          \   'source_filename': '.footemplate.md;'},
          \ { 'match_func': {x -> v:true},
          \   'source_func': function('TemplateFallback')},
          \]

No problem! From my perspective, having a discussion to settle the discussion is in itself an important contribution!

Great! Happy to help. Once I get around to sorting the HTML export and citations, these templates will be the last wishlist item before I'll be content with my notes setup so I'm looking forward to it!

@wasowski
Copy link
Contributor

Just an irritating comment (so late in the process): I also initially found lack of templates a missing feature, but completely forgot about it half a year down the road. It is very easy to create templates with snippets, so wiki.vim does not strictly need templates.

@funguscolander
Copy link
Contributor

funguscolander commented Jun 29, 2021

... It is very easy to create templates with snippets, so wiki.vim does not strictly need templates.

This was discussed in #46 (comment), the advantage of templates is that they are automatic and for a lot of users would be much easier to set up if there is a requirement to access document properties, as there is in the current spec.

A suggestion based on the discussion of the pagetitle definition in #46, ...

Apologise for adding this after the initial suggestion too, I realise it also may have been quite irritating. Got a bit carried away with your enthusiastic response about having discussions!

@anshul-march
Copy link

anshul-march commented Jun 29, 2021

hi folks

so I wrote a Lua function to add date to my journal entries, I mentioned that here in #160

as Karl had suggested that we should try to be compatible with Vim and older nVim, here's the same in VimL

fun! CurrentEntry()
  let filepath = g:wiki_root.'journal/'.strftime('%Y-%m-%d').'.md'
  if !filereadable(filepath)
    call writefile([strftime('# %a, %d %B %y')], filepath)
  endif
  exe 'edit' filepath
endfun

PS:

  • I just read the latest docs in the feature branch, and i see that you're already covering the date in the pre-defined variables. So this is getting covered already.

@lervag
Copy link
Owner Author

lervag commented Jun 29, 2021

@c-fergus This seems like a better method, ...

Good to hear we agree!

A suggestion based on ...; can there be an additional context element and variable ...?

Yes, I think we can have several context elements. Let me start the implementation now with the current specification, then it should be easy to expand after.

An alternative for the title might be to use the display text ... (i.e. [THIS TEXT](...) ... and [[...|THIS TEXT]] ...).

I propose to let this be "future work" for now; when we have a working version, then feel free to open a new issue with a feature request.

template_dict.match_function: applies if match_function returns true with the context as argument (this is only applicable if match_re is not defined).

I am not sure what you mean by "this is only applicable if match_re is not defined." In the example code ...

The point is that a specific "template" defines either match_re or match_func, not both. If you happen to define both, then only match_re is used. The point of this is that match_func is more generic and can do exactly what match_re does, and so there is no reason to allow both at the same time.

In the example, the g:wiki_templates includes three templates, the first two uses match_re, whereas the last one uses match_func.

@wasowski Just an irritating comment (so late in the process): I also initially found lack of templates a missing feature, but completely forgot about it half a year down the road. It is very easy to create templates with snippets, so wiki.vim does not strictly need templates.

Agreed, but I also agree with @c-fergus that the idea has its merits of being
automatic.

@anshul-march Thanks for the vimscript variant! Reg.

So while we're at it, can we have dynamic templates? What are you thoughts on this?

I'm not fully sure what you mean. Can you be more specific?

@anshul-march
Copy link

anshul-march commented Jun 29, 2021

So while we're at it, can we have dynamic templates? What are you thoughts on this?

I'm not fully sure what you mean. Can you be more specific?

Let's take the example from the docs

# {{wiki#template#case_title {pagetitle}}}
Created: {date} {time}

# Introduction

# Conclusion

I was thinking that just like {date}, we let user defined function to run inside the templates to generate text. This way users can customize the template however they want.

PS:

  • maybe there's already a way to do that, and I am just blind to it. Maybe the hint lies in wiki#template#case_title

@lervag
Copy link
Owner Author

lervag commented Jun 29, 2021

maybe there's already a way to do that, and I am just blind to it. Maybe the hint lies in wiki#template#case_title

Exactly: The proposed syntax is that {{func arg}} is interpolated to the value of func(arg).

@lervag
Copy link
Owner Author

lervag commented Jun 29, 2021

Ok, it's starting to take shape. Still too early for real testing, e.g. no interpolation of the source files yet. But the source function should already work quite well. The code should be quite easy to read - see here.

@lervag
Copy link
Owner Author

lervag commented Jun 29, 2021

I believe the only thing left is to implement the template file functionality, i.e. variable and function interpolation, as well as the file search with the ; suffix (as in :help file-searching). I will appreciate feedback already, but you should of course feel free to wait with testing until I've pushed a full implementation.

Please note the minor update of the specification; I've combined the template file variables with the context object.

@funguscolander
Copy link
Contributor

@lervag Please note the minor update of the specification; I've combined the template file variables with the context object.

Does this mean that the only difference between contexts and variables is function substitution? Is this difference arbitrary or can this also be added as a context to bring both to parity? Now that you've made this spec change I can't think of a reason to seperate variables and contexts other than implementation difficulties, but I would assume the easiest way to implement variables would be to either use the contexts defined in wiki#template#init or to call the same functions called by those contexts.

Apologies for any ambiguity in referring to "contexts," I mean to say the elements of the context dictionary.

@lervag
Copy link
Owner Author

lervag commented Jul 1, 2021

Does this mean ...

I don't really understand what you're asking. What it means, is that the variables available in the text file format are exactly the keys of the context object. I.e., the table presented under :help wiki-templates-context also fully describes the available variables for the text file format described in wiki-templates-format. Does this answer your question?

@lervag
Copy link
Owner Author

lervag commented Jul 1, 2021

Ok, now I believe everything should work. It would be nice if someone wanted to test. I've simplified the template source path for now; if things work and you think it will be useful, then I will be glad to consider more flexible ways of specifying the template source file.

As the new features should not break any old features, and since it does not interfere, I believe it is safe to merge already and then apply changes on top afterwards. This also makes it easier for everyone to test, since you don't need to checkout the feature branch.

@lervag lervag merged commit 152ec2f into master Jul 1, 2021
@lervag lervag deleted the feat/templates branch July 1, 2021 22:08
@lervag
Copy link
Owner Author

lervag commented Jul 1, 2021

I've pushed a couple of minor fixes/adjustments to the master branch as well, now.

Feel free to 1) continue discussing and giving feedback here, or 2) open new issues on specific problems or suggestions for the new template feature.

@funguscolander
Copy link
Contributor

funguscolander commented Jul 2, 2021

What it means...

Okay I see from your last commit how the function substitution works. It answers my question. I copied the docs example code exactly to test but couldn't get new files to adopt a template, I'll test again tonight.

@anshul-march
Copy link

anshul-march commented Jul 3, 2021

Hi Karl,

I tried using the template feature. This is how I am setting templates for my daily journal and this works.

let g:wiki_templates = [
      \ { 'match_re': strftime('%Y-%m-%d'),
      \   'source_filename': '/Users/anshul/wiki/journal/.template.md'},
      \]

I do not understand quite clearly how to use match_func.

  1. How do I pass filename to my function here.

    fun! NewFunc(x)
      if a:x == 'rust'
        return v:true
      else
        return v:false
      endif
    endfun
    
    let g:wiki_templates = [
          \ { 'match_func': NewFunc(x),
          \   'source_filename': '/Users/anshul/wiki/.template-rust.md'},
          \]

    Desired output: my template-rust to be sourced only when the name of the file is rust.

  2. How can I get the complete absolute path of the filename?

@funguscolander
Copy link
Contributor

funguscolander commented Jul 3, 2021

@anshul-march

  1. What I gather from :help *g:wiki_templates* (see help page):

    fun! NewFunc(context)
      if a:context.name == 'rust'
        return v:true
      else
        return v:false
      endif
    endfun
    
    let g:wiki_templates = [
          \ { 'match_func': function('NewFunc'),
          \   'source_filename': '/Users/anshul/wiki/.template-rust.md'},
          \]

    However I haven't managed to get this working so I'm not sure if it is correct.

  2. Use the 'path' context key, from :h *wiki-templates-context* (see help page).

@anshul-march
Copy link

Hi @c-fergus I just tested your suggestions, and this worked. Thank you!

@lervag
Copy link
Owner Author

lervag commented Jul 4, 2021

@anshul-march

This is how I am setting templates for my daily journal and this works. ...

I would adjust this, because you are not really defining a proper regex. This should be better, I think, as it should match any file whose name matches an ISO date (but not a page that contains a date):

let g:wiki_templates = [
      \ { 'match_re': '^\d\d\d\d-\d\d-\d\d$',
      \   'source_filename': '/Users/anshul/wiki/journal/.template.md'},
      \]

I do not understand quite clearly how to use match_func.

I believe @c-fergus answered this correctly. The point here is that the argument is a dictionary, and you need to access the correct keys (e.g. name and so on).

The value for the 'match_func' must be a so called FuncRef, i.e. function('FunctionName'); as was also correctly fixed by @c-fergus.

Note that the conditional can be trivially simplified:

if a:x == 'rust'
  return v:true
else                      ===>    return a:x == 'rust'
  return v:false
endif

Also, perhaps you wanted something "looser" than the name being exactly rust? E.g.:

function! NewFunc(ctx)
  return a:ctx.name =~ '[Rr]ust'
endfun

@c-fergus

However I haven't managed to get this working so I'm not sure if it is correct.

Feel free to ask if there is something in particular that does not work. It works for me, and it seems to work for @anshul-march..?

@anshul-march
Copy link

and it seems to work for @anshul-march..?

Yes, it works.

function! NewFunc(ctx)
  return a:ctx.name =~ '[Rr]ust'
endfun

this is great, I didn't even know one can do this

as it should match any file whose name matches an ISO date

yes, I didn't consider this case at all, thank you

I noticed that if i try to make a link with [[Rust]] it creates a file Rust.md.
And if try to make one with [[rust]] it creates rust.md

for uniformity, is there a way that the files are always created with lowercase?

what I mean, if I use [[Rust]] I get rust.md

@lervag
Copy link
Owner Author

lervag commented Jul 4, 2021

Yes; you want the g:wiki_map_create_page option. E.g.:

let g:wiki_map_create_page = 'ToLower'

function ToLower(name) abort
  return tolower(a:name)
endfunction

" In fact, since 'tolower()' already exists, you can probably use it directly
" without defining a custom function (but with a custom function you can do
" more, e.g. convert spaces to underscores):
let g:wiki_map_create_page = 'tolower'

@lervag
Copy link
Owner Author

lervag commented Jul 4, 2021

Sorry; this was partly wrong. You probably want that function, but you also want g:wiki_map_link_create.

@lervag
Copy link
Owner Author

lervag commented Jul 4, 2021

But; there is currently no feature to fully convert the link url. To do that, you need more heavy customization. So, the proper answer to your question is sort of no; sort of yes. To convert the wiki scheme url before opening it; i.e. make both [[RUst]] and [[RUst|Text here]] link to rust.md, then you need to change the g:wiki_resolver function. The default is here:

function! wiki#url#wiki#resolver(fname, origin) abort " {{{1
if empty(a:fname) | return a:origin | endif
" Extract the full path
let l:path = a:fname[0] ==# '/'
\ ? wiki#get_root() . a:fname
\ : (empty(a:origin)
\ ? wiki#get_root()
\ : fnamemodify(a:origin, ':p:h')) . '/' . a:fname
let l:path = wiki#paths#s(l:path)
" Determine the proper extension (if necessary)
let l:extensions = wiki#u#uniq_unsorted(
\ (exists('b:wiki.extension') ? [b:wiki.extension] : [])
\ + g:wiki_filetypes)
if index(l:extensions, fnamemodify(a:fname, ':e')) < 0
let l:path = l:path
let l:path .= '.' . l:extensions[0]
if !filereadable(l:path) && len(l:extensions) > 1
for l:ext in l:extensions[1:]
let l:newpath = l:path . '.' . l:ext
if filereadable(l:newpath)
let l:path = l:newpath
break
endif
endfor
endif
endif
return l:path
endfunction

So, this is more complex, but it is doable if you know some more Vimscript.

@lervag
Copy link
Owner Author

lervag commented Jul 4, 2021

Essentially, I guess one could implement a "converter" that is applied here as well similar to the g:wiki_map_... functions that transform the input fname or url or link (call it what you want) before passing it to the resolver function. I'm not sure if anyone would want to use it, though. I believe the g:wiki_map_link_create may be what you want, because it will enforce the lower case style and also allow you to keep the original text as the text part of the link.

@funguscolander
Copy link
Contributor

Feel free to ask if there is something in particular that does not work. It works for me, and it seems to work for @anshul-march..?

Sure, it's the regex that isn't working for me, everything else seems great. Using match_func works fine:

function! MatchIndex(dict)
  return a:dict.name == 'index'
endfunction

let g:wiki_templates = [
      \ { 'match_func': function('MatchIndex'),
      \   'source_filename': $HOME .  '/Documents/notes/templates/templateindex.rmd'},
      \]

Using match_re approximately as described in the docs does not work (the file is named "index.rmd" instead of "index.md"):

let g:wiki_templates = [
      \ { 'match_re': `index\.rmd'),
      \   'source_filename': $HOME .  '/Documents/notes/templates/templateindex.rmd'},
      \]

Just using 'index' as the regex match works but this also matches files that just have 'index' in the name which I don't want.

@lervag
Copy link
Owner Author

lervag commented Jul 5, 2021

Ah, yes - the regex doesn't work because the name attribute, which is the one matched against with match_re, does not contain the extension. So, in this particular case, you need to use the match_func. I think this is documented, but perhaps not clear enough? I pushed a minor clarification now.

@funguscolander
Copy link
Contributor

funguscolander commented Jul 6, 2021

Ah, yes - the regex doesn't work because the name attribute, which is the one matched against with match_re, does not contain the extension. So, in this particular case, you need to use the match_func...

Ah okay I think I'm sorted now then, I opened #167 to address the example code which prompted me to use the extensions.

@Traap
Copy link

Traap commented Jul 10, 2021

I've been unsuccessful using wiki templates. I am certain the error is mine. I have included my attempt to setup two templates. I've read the doc and this thread multiple times. Any help is appreciated.

let s:journal = g:wiki_root . '/.journal.md'
let s:template = g:wiki_root . '/.template.md'

let g:wiki_templates = [
    \ { 'match_re': '^\d\d\d\d-\d\d-\d\d$',
    \   'source_filename': s:journal},
    \ { 'match_re': '*',
    \   'source_filename': s:template}
    \]

I echoed g:wiki_templates does from both vim / neovim and the contents seems correct to me. I have verity two templates are in my g:wiki_root folder too. I get a blank .md file each time I create a new page. :(

@lervag
Copy link
Owner Author

lervag commented Jul 11, 2021

There's a mistake in your 'match_re'. Can you try this?

let s:journal = g:wiki_root . '/.journal.md'
let s:template = g:wiki_root . '/.template.md'

let g:wiki_templates = [
      \ { 'match_re': '^\d\d\d\d-\d\d-\d\d$',
      \   'source_filename': s:journal},
      \ { 'match_re': '.*',
      \   'source_filename': s:template}
      \]

" Or perhaps better:
let g:wiki_templates = [
      \ { 'match_re': '^\d\d\d\d-\d\d-\d\d$',
      \   'source_filename': s:journal},
      \ { 'match_func': {x -> v:true},
      \   'source_filename': s:template}
      \]

By the way, it would be useful to know your value of g:wiki_root as well.

@Traap
Copy link

Traap commented Jul 13, 2021

The first example worked for 2nd template only.

I used put= to gather the g:wiki values.

put=g:wiki_root
/home/traap/git/wiki

put=g:wiki_templates
g:wiki_templates:
{'match_re': '^\d\d\d\d-\d\d-\d\d$', 'source_filename': '/home/traap/git/wiki/.journal.md'}
{'match_re': '.*', 'source_filename': '/home/traap/git/wiki/.template.md'}

The second example did not work for either template:

put=g:wiki_root
/home/traap/git/wiki

put=g:wiki_templates
g:wiki_templates:
{'match_re': '^\d\d\d\d-\d\d-\d\d$', 'source_filename': '/home/traap/git/wiki/.journal.md'}
{'match_re': '*', 'source_filename': '/home/traap/git/wiki/.template.md'}

Journal files are located in /home/traap/git/wiki/journal/...

2021-07-13.md is an example file name.

I tried two journal entries as follows (both did not use the template).

  1. WikiJournal
  2. [some random text](2021-01-11)

Both test cases did create the a journal file with md extension.

@lervag
Copy link
Owner Author

lervag commented Jul 13, 2021

I'm sorry, but this is a little bit hard to understand. Perhaps you can open a new issue to make the thread more focussed and easier to follow? I also think it's good to avoid "spamming" this feature thread with a possible configuration assist or bug report.

If/When you open a new issue, please note:

  • I don't understand what you mean with put= to gather g:wiki values. It is much better to simply show me what you add to your vimrc file.

The second example did not work for either template:

If I understand your put output correctly, it seems you did not really try my second alternative (match_func instead of match_re).

Repository owner locked as resolved and limited conversation to collaborators Jul 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants