├── .gitignore ├── LICENSE ├── README.md ├── autoload └── stackoverflow.vim ├── doc └── stackoverflow.txt └── plugin └── stackoverflow.vim /.gitignore: -------------------------------------------------------------------------------- 1 | workings/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Michael O'Brien 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-stackoverflow 2 | 3 | A vim plugin that allows you to search Stack Overflow right from vim. 4 | 5 | Requires vim compiled with python support. You can check this by running ``vim 6 | --version | grep +python`` - if something is returned, you're good to go. 7 | 8 | ## Installation 9 | 10 | Use your plugin manager of choice. 11 | 12 | - [Pathogen](https://github.com/tpope/vim-pathogen) 13 | - `git clone https://github.com/mickaobrien/vim-stackoverflow ~/.vim/bundle/vim-stackoverflow` 14 | - [Vundle](https://github.com/gmarik/vundle) 15 | - Add `Plugin 'mickaobrien/vim-stackoverflow'` to .vimrc 16 | - Run `:PluginInstall` 17 | - [NeoBundle](https://github.com/Shougo/neobundle.vim) 18 | - Add `NeoBundle 'https://github.com/mickaobrien/vim-stackoverflow'` to .vimrc 19 | - Run `:NeoBundleInstall` 20 | - [vim-plug](https://github.com/junegunn/vim-plug) 21 | - Add `Plug 'https://github.com/mickaobrien/vim-stackoverflow'` to .vimrc 22 | - Run `:PlugInstall` 23 | 24 | ## Usage 25 | The plugin adds one command, ``:StackOverflow``. It can be called as follows 26 | 27 | ``:StackOverflow 'query'`` 28 | 29 | This will open a buffer with relevant Stack Overflow questions. ``o`` will 30 | toggle the questions open and closed. 31 | -------------------------------------------------------------------------------- /autoload/stackoverflow.vim: -------------------------------------------------------------------------------- 1 | function! TextEnableCodeSnip(filetype,start,end,textSnipHl) abort 2 | " Taken from http://vim.wikia.com/wiki/Different_syntax_highlighting_within_regions_of_a_file 3 | let ft=toupper(a:filetype) 4 | let group='textGroup'.ft 5 | if exists('b:current_syntax') 6 | let s:current_syntax=b:current_syntax 7 | " Remove current syntax definition, as some syntax files (e.g. cpp.vim) 8 | " do nothing if b:current_syntax is defined. 9 | unlet b:current_syntax 10 | endif 11 | execute 'syntax include @'.group.' syntax/'.a:filetype.'.vim' 12 | try 13 | execute 'syntax include @'.group.' after/syntax/'.a:filetype.'.vim' 14 | catch 15 | endtry 16 | if exists('s:current_syntax') 17 | let b:current_syntax=s:current_syntax 18 | else 19 | unlet b:current_syntax 20 | endif 21 | execute 'syntax region textSnip'.ft.' 22 | \ matchgroup='.a:textSnipHl.' 23 | \ start="'.a:start.'" end="'.a:end.'" 24 | \ contains=@'.group 25 | endfunction 26 | 27 | function! stackoverflow#StackOverflow(query) 28 | 29 | let query=a:query 30 | 31 | if exists('b:current_syntax') 32 | let ftype=b:current_syntax 33 | endif 34 | 35 | let winnum = bufwinnr('^__StackOverflow__') 36 | if (winnum >= 0) 37 | "execute winnum . 'wincmd w' 38 | execute winnum . 'wincmd c' 39 | "let ftype = split(bufname('%'), '__')[-1] 40 | endif 41 | "else 42 | let ftype = b:current_syntax 43 | let bufname = '__StackOverflow__' . ftype 44 | execute 'belowright 10split ' . bufname 45 | setlocal buftype=nofile 46 | setlocal nonumber 47 | "Map o to toggle fold open/close 48 | nnoremap o zak 49 | "endif 50 | normal! ggdG 51 | 52 | silent echom 'Searching for ' . query 53 | 54 | python << EOF 55 | import vim, urllib, urllib2, json, StringIO, gzip, re 56 | 57 | query = vim.eval("a:query") 58 | 59 | QUESTION_URL = "http://api.stackexchange.com/2.2/search/excerpts?order=desc&sort=relevance&q=%s&accepted=true&site=stackoverflow" 60 | ANSWER_URL = "http://api.stackexchange.com/2.2/questions/%s/?order=desc&sort=votes&site=stackoverflow&filter=!)Rw3MeNsaTmNs*UdDXqKh*Ci" 61 | TIMEOUT = 20 62 | 63 | def search(query): 64 | questions = get_questions(query) 65 | question_ids = [q['question_id'] for q in questions['items']] 66 | all_question_data = get_answers(question_ids) 67 | 68 | for a in all_question_data['items']: 69 | a['answers'] = sorted(a['answers'], key=lambda x: x['score'], reverse=True) 70 | return all_question_data 71 | 72 | def get_questions(query): 73 | url = QUESTION_URL % urllib.quote(query) 74 | questions = get_content(url) 75 | return questions 76 | 77 | def get_answers(question_ids): 78 | qids = ';'.join(map(str, question_ids)) 79 | url = ANSWER_URL % qids 80 | answers = get_content(url) 81 | return answers 82 | 83 | def html2list(html): 84 | clean = clean_html(html) 85 | split_text = clean.split('\n') 86 | return split_text 87 | 88 | def format_answers(answers): 89 | answerer = lambda x: x['owner']['display_name'] 90 | score = lambda x: x['score'] 91 | bodies = [80*'='+'\n' 92 | + 'Answered by: ' + answerer(a) + '\n' 93 | + 'Score: ' + str(score(a)) + '\n\n' 94 | + a['body'] for a in answers] 95 | split_text = [html2list(b) for b in bodies] 96 | text_list = [] 97 | map(text_list.extend, split_text) 98 | return text_list 99 | 100 | def get_content(url): 101 | try: 102 | response = urllib2.urlopen(url, None, TIMEOUT) 103 | 104 | if response.info().get('Content-Encoding') == 'gzip': 105 | buf = StringIO.StringIO( response.read()) 106 | gzip_f = gzip.GzipFile(fileobj=buf) 107 | content = gzip_f.read() 108 | else: 109 | content = response.read() 110 | 111 | json_response = json.loads(content) 112 | 113 | return json_response 114 | 115 | except Exception, e: 116 | print e 117 | 118 | def clean_html(html): 119 | codes = { 120 | r'': '', 121 | r'': '', 122 | r'': '', 123 | '
': '', 124 | r'(.*?)': r'\1', 125 | r'': '', 126 | r'': '', 127 | r'': '', 128 | r'': '\n', 129 | '': '', 130 | r'[\n]*[\n]*': '', 131 | r'
  • (.*?)
  • ': r'* \1', 132 | r'(.*?)': r'[\2](\1)', 133 | '"' : '"', 134 | ''': "'", 135 | '…': '...', 136 | '&': '&', 137 | '>': '>', 138 | '<': '<' 139 | } 140 | 141 | for code in codes: 142 | html = re.sub(code, codes[code], html) 143 | 144 | #TODO move encoding somewhere else! 145 | return html.encode('latin1', errors='ignore') 146 | 147 | vim.current.buffer[0] = "RESULTS FOR %s" % query 148 | questions = search(query)['items'] 149 | 150 | for i, q in enumerate(questions): 151 | # KEYS 152 | #[u'body', u'is_answered', u'question_score', u'tags', u'title', u'excerpt', u'last_activity_date', u'answer_count', u'creation_date', u'item_type', u'score', u'has_accepted_answer', u'is_accepted', u'question_id'] 153 | #vim.current.buffer.append(q) 154 | 155 | title = clean_html(q['title'].encode('latin1', errors='ignore')) 156 | answer_count = q['answer_count'] 157 | #excerpt = clean_html(q['excerpt'].encode('latin1').replace('\n', ' ')) 158 | 159 | vim.current.buffer.append("Q%d. %s (%d answers)" % (i+1, title, answer_count)) 160 | vim.current.buffer.append(html2list(q['body'])) 161 | answers = format_answers(q['answers']) 162 | #print answers 163 | vim.current.buffer.append(answers) 164 | 165 | 166 | EOF 167 | "if exists(ftype) 168 | call TextEnableCodeSnip(ftype, '', '', 'SpecialComment') 169 | "endif 170 | 171 | call SetFolds() 172 | endfunction 173 | 174 | function! MarkdownFolds() 175 | " Lines that start with Q1. start a fold 176 | let thisline = getline(v:lnum) 177 | if match(thisline, '^Q\d\{1,2\}\.') >= 0 178 | return ">1" 179 | else 180 | return "=" 181 | endif 182 | endfunction 183 | 184 | function! MarkdownFoldText() 185 | let foldsize = (v:foldend-v:foldstart) 186 | return getline(v:foldstart) 187 | endfunction 188 | 189 | function! SetFolds() 190 | setlocal foldmethod=expr 191 | setlocal foldexpr=MarkdownFolds() 192 | setlocal foldtext=MarkdownFoldText() 193 | setlocal foldcolumn=1 194 | endfunction 195 | -------------------------------------------------------------------------------- /doc/stackoverflow.txt: -------------------------------------------------------------------------------- 1 | *stackoverflow.txt* Plugin for querying Stack Overflow from vim. 2 | 3 | ============================================================================== 4 | INTRODUCTION 5 | 6 | vim-stackoverflow allows you to query Stack Overflow directly from vim. 7 | 8 | ============================================================================== 9 | COMMANDS 10 | 11 | vim-stackoverflow adds one command, StackOverflow. It is called as follows: 12 | 13 | :StackOverflow 'query string' Search Stack Overflow for 'query string' and 14 | display the results in a window below. Toggle 15 | the folded questions with o. 16 | 17 | ============================================================================== 18 | CHANGELOG 19 | 20 | v0.1.0 21 | * It works. Pretty much. 22 | 23 | ============================================================================== 24 | 25 | vim:ts=4:et:ft=help: 26 | -------------------------------------------------------------------------------- /plugin/stackoverflow.vim: -------------------------------------------------------------------------------- 1 | if !has('python') 2 | echo "Error: Required vim compiled with +python" 3 | finish 4 | endif 5 | 6 | if exists('g:loaded_stackoverflow') || &cp 7 | finish 8 | endif 9 | let g:loaded_stackoverflow = 1 10 | 11 | command! -nargs=1 StackOverflow call stackoverflow#StackOverflow() 12 | --------------------------------------------------------------------------------