" pastie.vim: Vim plugin for pastie.caboo.se
" Maintainer: Tim Pope <vimNOSPAM@tpope.info>
" URL:        http://www.vim.org/scripts/script.php?script_id=1624
" GetLatestVimScripts: 1624 1
" $Id: pastie.vim,v 1.15 2007-12-13 16:44:26 tpope Exp $

" Installation:
" Place in ~/.vim/plugin or vimfiles/plugin
" A working ruby install is required (Vim interface not necessary).

" Usage:
" :Pastie creates a new paste (example arguments shown below).  Use :w to save
" it by posting it to the server (the parser used is derived from the Vim
" filetype).  This updates the filename and stores the new url in the primary
" selection/clipboard when successful.  :Pastie!  creates a paste, saves, and
" closes the buffer, except when loading an existing paste.

" :Pastie                   Create a paste from all open windows
" :Pastie!                  Create a paste from all open windows and paste it
" :1,10Pastie               Create a paste from the specified range
" :%Pastie                  Use the entire current file to create a new paste
" :Pastie foo.txt bar.txt   Create a paste from foo.txt and bar.txt
" :Pastie! foo.txt          Paste directly from foo.txt
" :Pastie a                 Create a paste from the "a register
" :Pastie @                 Create a paste from the default (unnamed) register
" :Pastie *                 Create a paste from the primary selection/clipboard
" :Pastie _                 Create a new, blank paste
" :768Pastie                Load existing paste 768
" :0Pastie                  Load the newest paste
" :Pastie http://pastie.caboo.se/768            Load existing paste 768
" :Pastie http://pastie.caboo.se/123456?key=... Use login from pastie bot

" Regardless of the command used, on the first write, this script will create
" a new paste, and on subsequent writes, it will update the existing paste.
" If a bang is passed to a command that load an existing paste (:768), the
" first write will update as well.  If the loaded paste was not created in the
" same vim session, or with an account extracted from your Firefox cookies,
" updates will almost certainly silently fail.  (Advanced users can muck
" around with g:pastie_session_id if desired).

" As hinted at earlier, pastie.vim will snoop around in your Firefox cookies,
" and use an account cookie if one is found.  The only way to create one of
" these account cookies is by talking to pastie on IRC.

" At the shell you can directly create a new pastie with a command like
" $ vim +Pastie
" or, assuming no other plugins conflict
" $ vim +Pa
" And, to read an existing paste
" $ vim +768Pa
" You could even paste a file directly
" $ vim '+Pa!~/.irbrc' +q
" You can even edit a pastie URL directly, but this is not recommended because
" netrw can sometimes interfere.

" Lines ending in #!! will be sent as lines beginning with !!.  This alternate
" format is easier to read and is less likely to interfere with code
" execution.  In Vim 7 highlighting is done with :2match (use ":2match none"
" to disable it) and in previous versions, :match (use ":match none" to
" disable).
"
" Known Issues:
" URL sometimes disappears with the bang (:Pastie!) variant.  You can still
" retrieve it from the clipboard.

if exists("g:loaded_pastie") || &cp
    finish
endif
let g:loaded_pastie = 1

augroup pastie
    autocmd!
    autocmd BufReadPre  http://pastie.caboo.se/*[0-9]?key=*           call s:extractcookies(expand("<amatch>"))
    autocmd BufReadPost http://pastie.caboo.se/*[0-9]?key=*           call s:PastieSwapout(expand("<amatch>"))
    autocmd BufReadPost http://pastie.caboo.se/*[0-9]                 call s:PastieSwapout(expand("<amatch>"))
    autocmd BufReadPost http://pastie.caboo.se/pastes/*[0-9]/download call s:PastieRead(expand("<amatch>"))
    autocmd BufReadPost http://pastie.caboo.se/*[0-9].*               call s:PastieRead(expand("<amatch>"))
    autocmd BufWriteCmd http://pastie.caboo.se/pastes/*[0-9]/download call s:PastieWrite(expand("<amatch>"))
    autocmd BufWriteCmd http://pastie.caboo.se/*[0-9].*               call s:PastieWrite(expand("<amatch>"))
    autocmd BufWriteCmd http://pastie.caboo.se/pastes/                call s:PastieWrite(expand("<amatch>"))
augroup END

let s:domain = "pastie.caboo.se"

let s:dl_suffix = ".txt" " Used only for :file

if !exists("g:pastie_destination")
    if version >= 700
        let g:pastie_destination = 'tab'
    else
        let g:pastie_destination = 'window'
    endif
    "let g:pastie_destination = 'buffer'
endif

command! -bar -bang -nargs=* -range=0 -complete=file Pastie :call s:Pastie(<bang>0,<line1>,<line2>,<count>,<f-args>)

function! s:Pastie(bang,line1,line2,count,...)
    if exists(":tab")
        let tabnr = tabpagenr()
    endif
    let newfile = "http://".s:domain."/pastes/"
    let loggedin = 0
    let ft = &ft
    let num = 0
    if a:0 == 0 && a:count == a:line1 && a:count > line('$')
        let num = a:count
    elseif a:0 == 0 && a:line1 == 0 && a:line2 == 0
        let num = s:latestid()
        if num == 0
            return s:error("Could not determine latest paste")
        endif
    elseif !a:count && a:0 == 1
        if a:1 == '*'
            let numcheck = @*
        elseif a:1 == '+'
            let numcheck = @+
        elseif a:1 == '@'
            let numcheck = @@
        else
            let numcheck = a:1
        endif
        let numcheck = substitute(numcheck,'\n\+$','','')
        let numcheck = substitute(numcheck,'^\n\+','','g')
        if numcheck =~ '\n'
            let numcheck = ''
        endif
        if numcheck =~ '^\d\d+$'
            let num = numcheck
        elseif numcheck =~ '\%(^\|/\)\d\+?key=\x\{8,\}'
            if exists("b:pastie_fake_login")
                unlet b:pastie_fake_login
            else
                call s:extractcookies('/'.matchstr(numcheck,'\%(^\|/\)\zs\d\+?.*'))
            endif
            if exists("g:pastie_account")
                let loggedin = 1
            endif
            let num = matchstr(numcheck,'\%(^\|/\)\zs\d\+\ze?')
        elseif numcheck =~ '\%(^\|^/\|^http://.*\)\d\+\%([/?]\|$\)'
            let num = matchstr(numcheck,'\%(^\|/\)\zs\d\+')
        endif
    endif
    if num
        call s:newwindow()
        let file = "http://".s:domain."/".num.s:dl_suffix
        silent exe 'doautocmd BufReadPre '.file
        silent exe 'read !ruby -rnet/http -e "r = Net::HTTP.get_response(\%{'.s:domain.'}, \%{/pastes/'.num.'/download}); if r.code == \%{200} then print r.body else exit 10+r.code.to_i/100 end"'
        if v:shell_error && v:shell_error != 14 && v:shell_error !=15
            return s:error("Something went wrong: shell returned ".v:shell_error)
        else
            let err = v:shell_error
            silent exe "file ".file
            1d_
            set nomodified
            call s:dobufreadpost()
            if err
                if loggedin
                    let b:pastie_update = 1
                else
                    echohl WarningMsg
                    echo "Warning: Failed to retrieve existing paste"
                    echohl None
                endif
            endif
            "call s:PastieRead(file)
            if a:bang
                " Instead of saving an identical paste, take ! to mean "do not
                " create a new paste on first save"
                let b:pastie_update = 1
            endif
            return
        endif
    elseif a:0 == 0 && !a:count && a:bang && expand("%") =~ '^http://'.s:domain.'/\d\+'
        " If the :Pastie! form is used in an existing paste, switch to
        " updating instead of creating.
        "echohl Question
        echo "Will update, not create"
        echohl None
        let b:pastie_update = 1
        return
    elseif a:0 == 1 && !a:count && a:1 =~ '^[&?]\x\{32,\}'
        " Set session id with :Pastie&deadbeefcafebabe
        let g:pastie_session_id = strpart(a:1,1)
    elseif a:0 == 1 && !a:count && (a:1 == '&' || a:1 == '?')
        " Extract session id with :Pastie&
        call s:cookies()
        if exists("g:pastie_session_id")
            echo g:pastie_session_id
            "silent! let @* = g:pastie_session_id
        endif
    elseif a:0 == 0 && !a:count && a:line1
        let ft = 'conf'
        let sum = ""
        let cnt = 0
        let keep = @"
        windo let tmp = s:grabwin() | if tmp != "" | let cnt = cnt + 1 | let sum = sum . tmp | end
        let sum = substitute(sum,'\n\+$',"\n",'')
        if cnt == 1
            let ft = matchstr(sum,'^##.\{-\} \[\zs\w*\ze\]')
            if ft != ""
                let sum = substitute(sum,'^##.\{-\} \[\w*\]\n','','')
            endif
        endif
        call s:newwindow()
        silent exe "file ".newfile
        "silent exe "doautocmd BufReadPre ".newfile
        if sum != ""
            let @" = sum
            silent $put
            1d _
        endif
        if ft == 'plaintext' || ft == 'plain_text'
            "set ft=conf
        elseif ft != '' && sum != ""
            let &ft = ft
        endif
        let @" = keep
        call s:dobufreadpost()
    else
        let keep = @"
        let args = ""
        if a:0 > 0 && a:1 =~ '^[-"@0-9a-zA-Z:.%#*+~_/]$'
            let i = 1
            let register = a:1
        else
            let i = 0
            let register = ""
        endif
        while i < a:0
            let i = i+1
            if strlen(a:{i})
                let file = fnamemodify(expand(a:{i}),':~:.')
                let args = args . file . "\n"
            endif
        endwhile
        let range = ""
        if a:count
            silent exe a:line1.",".a:line2."yank"
            let range = @"
            let @" = keep
        endif
        call s:newwindow()
        silent exe "file ".newfile
        "silent exe "doautocmd BufReadPre ".newfile
        if range != ""
            let &ft = ft
            let @" = range
            silent $put
        endif
        if register != '' && register != '_'
            "exe "let regvalue = @".register
            silent exe "$put ".(register =~ '^[@"]$' ? '' : register)
        endif
        while args != ''
            let file = matchstr(args,'^.\{-\}\ze\n')
            let args = substitute(args,'^.\{-\}\n','','')
            let @" = "## ".file." [".s:parser(file)."]\n"
            if a:0 != 1 || a:count
                silent $put
            else
                let &ft = s:filetype(file)
            endif
            silent exe "$read ".substitute(file,' ','\ ','g')
        endwhile
        let @" = keep
        1d_
        call s:dobufreadpost()
        if (a:0 + (a:count > 0)) > 1
            set ft=conf
        endif
    endif
    1
    call s:afterload()
    if a:bang
        write
        let name = bufname('%')
        " TODO: re-echo the URL in a way that doesn't disappear. Stupid Vim.
        silent! bdel
        if exists("tabnr")
            silent exe "norm! ".tabnr."gt"
        endif
    endif
endfunction

function! s:dobufreadpost()
    if expand("%") =~ '/\d\+\.\@!'
        silent exe "doautocmd BufReadPost ".expand("%")
    else
        silent exe "doautocmd BufNewFile ".expand("%")
    endif
endfunction

function! s:PastieSwapout(file)
    if a:file =~ '?key='
        let b:pastie_fake_login = 1
    endif
    exe "Pastie ".a:file
endfunction

function! s:PastieRead(file)
    let lnum = line(".")
    silent %s/^!!\(.*\)/\1 #!!/e
    exe lnum
    set nomodified
    let num = matchstr(a:file,'/\@<!/\zs\d\+')
    let url = "http://".s:domain."/pastes/".num
    "let url = substitute(a:file,'\c/\%(download/\=\|text/\=\)\=$','','')
    let url = url."/download"
    let result = system('ruby -rnet/http -e "puts Net::HTTP.get_response(URI.parse(%{'.url.'}))[%{Content-Disposition}]"')
    let fn = matchstr(result,'filename="\zs.*\ze"')
    let &ft = s:filetype(fn)
    if &ft =~ '^\%(html\|ruby\)$' && getline(1).getline(2).getline(3) =~ '<%'
        set ft=eruby
    endif
    call s:afterload()
endfunction

function! s:afterload()
    set commentstring=%s\ #!! "
    hi def link pastieIgnore    Ignore
    hi def link pastieNonText   NonText
    if exists(":match")
        hi def link pastieHighlight MatchParen
        if version >= 700
            2match pastieHighlight /^!!\s*.*\|^.\{-\}\ze\s*#!!\s*$/
        else
            match  pastieHighlight /^!!\s*.*\|^.\{-\}\ze\s*#!!\s*$/
        endif
    else
        hi def link pastieHighlight Search
        syn match pastieHighlight '^.\{-\}\ze\s*#!!\s*$' nextgroup=pastieIgnore skipwhite
        syn region pastieHighlight start='^!!\s*' end='$' contains=pastieNonText
    endif
    syn match pastieIgnore '#!!\ze\s*$' containedin=rubyComment,rubyString
    syn match pastieNonText '^!!' containedin=rubyString
endfunction

function! s:PastieWrite(file)
    let parser = s:parser(&ft)
    let tmp = tempname()
    let num = matchstr(a:file,'/\@<!/\zs\d\+')
    if num == ''
        let num = 'pastes'
    endif
    if exists("b:pastie_update") && s:cookies() != '' && num != ""
        let url = "/pastes/".num
        let method = "_method=put&"
    else
        let url = "/pastes"
        let method = ""
    endif
    if exists("b:pastie_display_name")
        let pdn = "&paste[display_name]=".s:urlencode(b:pastie_display_name)
    elseif exists("g:pastie_display_name")
        let pdn = "&paste[display_name]=".s:urlencode(g:pastie_display_name)
    else
        let pdn = ""
    endif
    silent exe "write ".tmp
    let result = ""
    let rubycmd = 'obj = Net::HTTP.start(%{'.s:domain.'}){|h|h.post(%{'.url.'}, %q{'.method.'paste[parser]='.parser.pdn.'&paste[authorization]=burger&paste[key]=&paste[body]=} + File.read(%q{'.tmp.'}).gsub(/^(.*?) *#\!\! *#{36.chr}/,%{!\!}+92.chr+%{1}).gsub(/[^a-zA-Z0-9_.-]/n) {|s| %{%%%02x} % s[0]},{%{Cookie} => %{'.s:cookies().'}})}; print obj[%{Location}].to_s+%{ }+obj[%{Set-Cookie}].to_s'
    let result = system('ruby -rnet/http -e "'.rubycmd.'"')
    let redirect = matchstr(result,'^[^ ]*')
    let cookies  = matchstr(result,'^[^ ]* \zs.*')
    call s:extractcookiesfromheader(cookies)
    call delete(tmp)
    if redirect =~ '^\w\+://'
        set nomodified
        let b:pastie_update = 1
        "silent! let @+ = result
        silent! let @* = redirect
        silent exe "file ".redirect.s:dl_suffix
        " TODO: make a proper status message
        echo '"'.redirect.'" written'
        silent exe "doautocmd BufWritePost ".redirect.s:dl_suffix
    else
        if redirect == ''
            let redirect = "Could not post to ".url
        endif
        let redirect = substitute(redirect,'^-e:1:\s*','','')
        call s:error(redirect)
    endif
endfunction

function! s:error(msg)
    echohl Error
    echo a:msg
    echohl NONE
    let v:errmsg = a:msg
endfunction

function! s:filetype(type)
    " Accepts a filename, extension, pastie parser, or vim filetype
    let type = tolower(substitute(a:type,'.*\.','',''))
    if type =~ '^\%(x\=html\|asp\w*\)$'
        return 'html'
    elseif type =~ '^\%(eruby\|erb\|rhtml\)$'
        return 'eruby'
    elseif type =~ '^\%(ruby\|ruby_on_rails\|rb\|rake\|builder\|rjs\|irbrc\)'
        return 'ruby'
    elseif type == 'js' || type == 'javascript'
        return 'javascript'
    elseif type == 'c' || type == 'cpp' || type == 'c++'
        return 'cpp'
    elseif type =~ '^\%(css\|diff\|java\|php\|python\|sql\|sh\|shell-unix-generic\)$'
        return type
    else
        return ''
    endif
endfunction

function! s:parser(type)
    let type = s:filetype(a:type)
    if type == 'text' || type == ''
        return 'plain_text'
    elseif type == 'eruby'
        return 'html_rails'
    elseif type == 'ruby'
        return 'ruby_on_rails'
    elseif type == 'sh'
        return 'shell-unix-generic'
    elseif type == 'cpp'
        return 'c++'
    else
        return type
    endif
endfunction

function! s:grabwin()
    let ft = (&ft == '' ? expand("%:e") : &ft)
    let top = "## ".expand("%:~:.")." [".s:parser(ft)."]\n"
    let keep = @"
    silent %yank
    let file = @"
    let @" = keep
    if file == "" || file == "\n"
        return ""
    else
        return top.file."\n"
    endif
endfunction

function! s:cookies()
    if exists("g:pastie_session_id")
        let cookies = "_pastie_session_id=".g:pastie_session_id
    else
        call s:extractcookies('/')
        if !exists("g:pastie_session_id")
            if !exists("s:session_warning")
                echohl WarningMsg
                echo "Warning: could not extract session id"
                let s:session_warning = 1
                echohl NONE
            endif
            let cookies = ""
        else
            let cookies = "_pastie_session_id=".g:pastie_session_id
        endif
    endif
    if !exists("g:pastie_account")
        let rubycmd = '%w(~/.mozilla/firefox ~/.firefox/default ~/.phoenix/default ~/Application\ Data/Mozilla/Firefox/Profiles ~/Library/Application\ Support/Firefox/Profiles)'
        let rubycmd = rubycmd . '.each {|dir| Dir[File.join(File.expand_path(dir),%{*})].select {|p| File.exists?(File.join(p,%{cookies.txt}))}.each {|p| File.open(File.join(p,%{cookies.txt})).each_line { |l| a=l.split(9.chr); puts [a[4],a[6]].join(%{ }) if a[0] =~ /pastie\.caboo\.se#{36.chr}/ && Time.now.to_i < a[4].to_i && a[5] == %{account} }}}'
        let output = ''
        let output = system('ruby -e "'.rubycmd.'"')
        if output =~ '\n' && output !~ '-e:'
            let output = substitute(output,'\n.*','','')
            let g:pastie_account = matchstr(output,' \zs.*')
            let g:pastie_account_expires = matchstr(output,'.\{-\}\ze ')
        else
            let g:pastie_account = ''
        endif
    endif
    if exists("g:pastie_account") && g:pastie_account != ""
        " You cannot set this arbitrarily, it must be a valid cookie
        let cookies = cookies . (cookies == "" ? "" : "; ")
        let cookies = cookies . 'account='.substitute(g:pastie_account,':','%3A','g')
    endif
    return cookies
endfunction

function! s:extractcookies(path)
    let path = substitute(a:path,'\c^http://'.s:domain,'','')
    if path !~ '^/'
        let path = '/'.path
    endif
    let cookie = system('ruby -rnet/http -e "print Net::HTTP.get_response(%{'.s:domain.'},%{'.path.'})[%{Set-Cookie}]"')
    if exists("g:pastie_debug")
        let g:pastie_cookies_path = path
        let g:pastie_cookies = cookie
    endif
    return s:extractcookiesfromheader(cookie)
endfunction

function! s:extractcookiesfromheader(cookie)
    let cookie = a:cookie
    if cookie !~ '-e:'
        let session_id = matchstr(cookie,'\<_pastie_session_id=\zs.\{-\}\ze\%([;,]\|$\)')
        let account    = matchstr(cookie,'\<account=\zs.\{-\}\ze\%([;,]\|$\)')
        if session_id != ""
            let g:pastie_session_id = session_id
        endif
        if account != ""
            let g:pastie_account = account
            let time = matchstr(cookie,'\<[Ee]xpires=\zs\w\w\w,.\{-\}\ze\%([;,]\|$\)')
            if time != ""
                let g:pastie_account_expires = system('ruby -e "print Time.parse(%{'.time.'}).to_i"')
            endif
        endif
    endif
endfunction

function! s:latestid()
    return system('ruby -rnet/http -e "print Net::HTTP.get_response(URI.parse(%{http://'.s:domain.'/all})).body.match(%r{<a href=.http://'.s:domain.'/(\d+).>View})[1]"')
endfunction

function! s:urlencode(str)
    " Vim 6.2, how did we ever live with you?
    return substitute(substitute(a:str,"[\001-\037%&?=\\\\]",'\="%".printf("%02X",char2nr(submatch(0)))','g'),' ','%20','g')
endfunction

function! s:newwindow()
    if !(&modified) && (expand("%") == '' || (version >= 700 && winnr("$") == 1 && tabpagenr("$") == 1))
        enew
    else
        if g:pastie_destination == 'tab'
            tabnew
        elseif g:pastie_destination == 'window'
            new
        else
            enew
        endif
    endif
    setlocal noswapfile
endfunction

" vim:set sw=4 sts=4 et:
