"=============================================================================
" File    : autoload/unite/sources/outline/defaults/lisp.vim
" Author  : adolenc <andrej.dolenc@student.uni-lj.si>
" Updated : 2016-04-08
"
" Licensed under the MIT license:
" http://www.opensource.org/licenses/mit-license.php
"
"=============================================================================

function! unite#sources#outline#defaults#lisp#outline_info() abort
  return s:outline_info
endfunction

let s:Util = unite#sources#outline#import('Util')

"-----------------------------------------------------------------------------
" Outline Info

let s:DEF_PATTERN = '\(\S*:\)\?def\S*'
let s:FEATURE_PATTERN = '#\(+\|-\)\(([^)]\+)\|\S[^(]*\)'

let s:outline_info = {
      \ 'heading'  : '^' . s:FEATURE_PATTERN . '\s*(\|^(\|^\s*(' . s:DEF_PATTERN,
      \
      \ 'skip': {
      \   'header': '^;',
      \   'block': ['^\s*"', '[^\\]"'],
      \ },
      \
      \ 'not_match_patterns': [
      \   s:Util.shared_pattern('*', 'parameter_list'),
      \ ],
      \
      \ 'heading_groups': {
      \   'function'           : ['defun', 'defmacro', 'defgeneric', 'defmethod'],
      \   'variable'           : ['defvar', 'defparameter', 'defconstant'],
      \   'type'               : ['defclass', 'defstruct', 'define-condition', 'deftype'],
      \   'method-combination' : ['define-method-combination'],
      \   'compiler-macro'     : ['define-compiler-macro'],
      \   'package'            : ['defpackage'],
      \ },
      \
      \ 'highlight_rules': [
      \   { 'name'     : 'function',
      \     'pattern'  : '/ [^ ]* \zs.*/' },
      \   { 'name'     : 'type',
      \     'pattern'  : '/' . s:DEF_PATTERN . '/' },
      \   { 'name'     : 'special',
      \     'pattern'  : '/.*:: toplevel form/' },
      \ ],
      \}

function! s:outline_info.create_heading(which, heading_line, matched_line, context) abort
  let first_form = s:remove_feature_check(a:heading_line)
  let heading = {
        \ 'word' : s:splice_form(first_form),
        \ 'level': s:Util.get_indent_level(a:context, a:context.heading_lnum),
        \ 'type' : matchstr(first_form, '^\s*(\zs\S\+\ze'),
        \ }
  let form_args = matchstr(heading.word, '^\S\+\s\+\zs.*')

  " Whether we are checking a def* or a top-level form, fix the heading by
  " appending appropriate type information after `::'.
  if heading.type =~ '^' . s:DEF_PATTERN
    let heading.word = heading.type . ' ' . s:add_ldots(form_args)
  else
    let heading.word = s:add_ldots(heading.word) . ' :: toplevel form'
  endif
  return heading
endfunction

function! s:count_occurences(char, string) abort
  " Count number of occurences of {char} in {string}.
  return len(split(a:string, a:char, 1)) - 1
endfunction

function! s:has_balanced_chars(line, open, close) abort
  " Check whether {line} has balanced {open} and {close} characters.
  " Actually just fake it by checking if they appear same number of times
  let n_open_chars = s:count_occurences(a:open, a:line)
  if a:open == a:close
    return (n_open_chars % 2) == 0
  endif
  let n_close_chars = s:count_occurences(a:close, a:line)
  return n_open_chars == n_close_chars
endfunction

function! s:splice_form(line) abort
  " Remove the outermost parentheses from {line}.
  if s:has_balanced_chars(a:line, '(', ')')
    return matchstr(a:line, '^\s*(\zs.*\ze)\s*$')
  else
    return matchstr(a:line, '^\s*(\zs.*')
  endif
endfunction

function! s:remove_feature_check(line) abort
  " Remove the (#+|#-)[feature] expression from {line} if it exists.
  return matchstr(a:line, '^\(' . s:FEATURE_PATTERN . '\)\?\zs.*')
endfunction

function! s:add_ldots(line) abort
  " Add `...' to the end of {line} in case it has unbalanced parentheses or
  " quotes.
  if !s:has_balanced_chars(a:line, '(', ')') || !s:has_balanced_chars(a:line, '"', '"')
    return a:line . ' ...'
  endif
  return a:line
endfunction