├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── doc └── gso.txt ├── plugin ├── gso.vim └── gso │ ├── __init__.py │ ├── load_up_answers.py │ └── search_google.py └── tools └── gso.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | dist 3 | build 4 | nohup.out 5 | run 6 | *.pyc 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for testing the full vim plugin 2 | FROM buildpack-deps:xenial 3 | 4 | RUN apt-get update && apt-get install -y --no-install-recommends \ 5 | python-dev \ 6 | vim-nox-py2 7 | 8 | RUN wget https://bootstrap.pypa.io/get-pip.py && \ 9 | python get-pip.py && \ 10 | rm get-pip.py 11 | 12 | RUN pip install --no-cache-dir \ 13 | google-api-python-client \ 14 | Cython \ 15 | py-stackexchange \ 16 | lxml 17 | 18 | WORKDIR /gso 19 | 20 | COPY . . 21 | 22 | # Install Vundle with GSO 23 | RUN git clone --depth=1 https://github.com/VundleVim/Vundle.vim.git $HOME/.vim/bundle/Vundle.vim && \ 24 | wget https://raw.githubusercontent.com/VundleVim/Vundle.vim/11fdc428fe741f4f6974624ad76ab7c2b503b73e/test/minirc.vim -O $HOME/.vimrc && \ 25 | sed -i "7i Plugin 'file:///gso/'" $HOME/.vimrc && \ 26 | vim +PluginInstall +qall 27 | 28 | 29 | WORKDIR /workspace 30 | 31 | CMD ["/bin/bash"] 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License (MIT) 2 | 3 | Copyright (c) <2015> 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GSO 2 | *Googling Stack Overflow* 3 | [![Screencast](http://i.imgur.com/feBUqnJ.gif)](https://asciinema.org/a/123375) 4 | 5 | Thank you [Daniel](https://stackoverflow.com/a/35754890/2689923), 6 | [Brionius](https://stackoverflow.com/a/18262384/2689923), 7 | and [bytecode77](https://stackoverflow.com/a/29915909/2689923)! 8 | 9 | GSO now work exclusively in Python 3. Confirmed to work in NeoVim. 10 | 11 | *(GSO now also prints the answer URL.)* 12 | 13 | ## Installation 14 | 15 | *(If you just want to try, 16 | there are demo keys at the bottom of this page. 17 | They are shared, so play nice.)* 18 | 19 | Make sure your vim supports python scripting (`vim --version | grep +python3` should return something). 20 | If this doesn't work, the `vim-nox-py2` package on ubuntu has this (`sudo apt-get install vim-nox-py2`), 21 | else, look to [SO](https://vi.stackexchange.com/questions/10242/vim-8-0-python-support). 22 | 23 | Then, install python dependencies: 24 | 25 | ```` 26 | pip3 install google-api-python-client Cython py-stackexchange lxml 27 | ```` 28 | 29 | If there are issues with installing `lxml`, it's probably to do with a missing `libxml` library. 30 | The [`lxml`](http://lxml.de/installation.html) site has some help for this. 31 | 32 | Get API keys for [Google Custom Search](https://developers.google.com/custom-search/json-api/v1/overview) 33 | (scroll to API key), and [Stack Apps](https://stackapps.com/apps/oauth/register). 34 | This is free, don't be intimidated by the forms! 35 | Enter *whatever* in the boxes, and the key generated for you will be compatible with this app. 36 | Trust me, it's worth it. 37 | 38 | Put these into 39 | environment variables `GOOGLE_KEY` and 40 | `SE_KEY`, respectively (e.g., `export GOOGLE_KEY="......"`). 41 | 42 | (Vundle) Add this repo to your `.vimrc` file: 43 | 44 | ```` 45 | Plugin 'MilesCranmer/gso' 46 | ```` 47 | 48 | Then, just `:PluginInstall` in vim. 49 | 50 | **(optional)** Map Ctrl-E to type ":GSO " for you, by putting the following in your `.vimrc`: 51 | ```vim 52 | nnoremap :GSO 53 | ``` 54 | 55 | Usage 56 | ----- 57 | 58 | ```` 59 | :GSO [(-l | --language) ] [-n | --no-text] [...] 60 | ```` 61 | 62 | 63 | For example, in a file `sort.py`, run: 64 | 65 | ```` 66 | :GSO Do a bubble sort 67 | ```` 68 | 69 | And watch the python code get dumped below your cursor. 70 | GSO will append the language to your query by the file extension, but you can set it explicitly by: 71 | 72 | ```` 73 | :GSO -l haskell Generate a fibonacci sequence 74 | ```` 75 | 76 | FAQ 77 | --- 78 | 79 | - The Python compiled into my vim is saying it can't see googleapi-client, what do I do? 80 | 81 | I have a similar problem and made a hack around it. Your gso should be installed into ~/.vim/gso. Edit the file ~/.vim/gso/plugin/gso.vim, and after each `python << EOF`, paste the following lines: 82 | 83 | ``` 84 | import sys 85 | sys.path.append('....') 86 | ``` 87 | 88 | Replace the inside of the string with the "site-packages" directory where googleapi-client is installed. Make sure you use python 3 to install the packages, or optionally replace the `python3 << EOF` with `python << EOF`. 89 | 90 | ## Tools 91 | 92 | There is a shell utility in `tools`. It simply calls the GSO command and dumps the result to the /dev/stdout. 93 | Copy it to `/usr/bin/gso` (or anywhere on the `PATH`), then call it as you normally would: 94 | 95 | ```bash 96 | ➜ gso How to change the url of a git remote 97 | 98 | GSO>>> 99 | You can 100 | git remote set-url origin git://new.url.here 101 | 102 | 103 | (see git help remote) or you can just edit .git/config and change the 104 | URLs there. You're not in any danger of losing history unless you do 105 | something very silly (and if you're worried, just make a copy of your 106 | repo, since your repo is your history.) 107 | << 4 | License: MIT 5 | 6 | ========================================================================== 7 | Commands *gso-commands* 8 | 9 | These commands are local to the buffers in which they are called. 10 | 11 | 12 | *GSO* *:GSO* 13 | 14 | SYNOPSIS 15 | 16 | :GSO [(-l | --language) ] [-n | --no-text] [...] 17 | 18 | DESCRIPTION 19 | 20 | Search Google with the query [search], find 21 | the first relevant Stack Exchange result, 22 | then dump the answer with the highest score 23 | to the line below the cursor, in plain 24 | text format. The following [flags] are 25 | available: 26 | 27 | -l , --language 28 | 29 | Set the programming language for the 30 | search, if it is different than the 31 | current syntax highlighting language. 32 | You can check this with (:echo &ft). 33 | Note that some languages (e.g., C++) 34 | are mapped from a different code (for 35 | C++, it is from cpp) 36 | 37 | Set to be 'none', 'nothing', 38 | or 'no' for no language to be appended 39 | to your search query. 40 | 41 | -n , --no-text 42 | 43 | Don't paste any of the answer text, 44 | just the code. 45 | 46 | ========================================================================== 47 | About *gso-about* 48 | 49 | Checkout the latest version on GitHub at the following URL: 50 | https://github.com/MilesCranmer/GooglingStackOverflow.vim 51 | 52 | vim:tw=78:et:ft=help:norl: 53 | -------------------------------------------------------------------------------- /plugin/gso.vim: -------------------------------------------------------------------------------- 1 | python3 << EOF 2 | 3 | import sys 4 | import vim 5 | 6 | sys.path.insert(0, vim.eval("expand(':p:h')")) 7 | 8 | if "gso" in sys.modules: 9 | from importlib import reload 10 | gso = reload(gso) 11 | else: 12 | import gso 13 | EOF 14 | 15 | function! GSO(...) 16 | 17 | let all_args=a:000 18 | 19 | python3 << EOF 20 | 21 | import vim 22 | import os 23 | import argparse 24 | from io import BytesIO 25 | from lxml import etree 26 | from gso import load_up_answers, load_up_questions 27 | 28 | # ["___", "____"] - interpreted as block comment 29 | # "___" - interpreted as single-line comment 30 | comments = { 31 | 'python': ["\"\"\"", "\"\"\""], 32 | 'haskell': ["{-", "-}"], 33 | 'cpp': ["/*", "*/"], 34 | 'c++': ["/*", "*/"], 35 | 'c': ["/*", "*/"], 36 | 'cuda': ["/*", "*/"], 37 | 'java': ["/*", "*/"], 38 | 'rust': ["/*", "*/"], 39 | 'php': ["/*", "*/"], 40 | 'javascript': ["/*", "*/"], 41 | 'ruby': ["=begin ", "=end "], 42 | 'perl': ["=begin ", "=cut "], 43 | 'tex': "%", 44 | 'plaintex': "%", 45 | 'latex': "%", 46 | 'html': [""], 47 | 'sh': "#", 48 | 'bash': "#", 49 | 'zsh': "#", 50 | 'shell': "#", 51 | 'make': "#", 52 | 'vim': "\" " 53 | } 54 | 55 | # Some filetypes from vim 56 | # should be searched with a different 57 | # name. 58 | search_mapping = { 59 | 'cpp': 'C++', 60 | 'sh': 'shell script', 61 | 'make': 'makefile' 62 | } 63 | 64 | 65 | """Load up options""" 66 | 67 | all_args = vim.eval("all_args") 68 | 69 | """Get default language""" 70 | curr_lang = "" 71 | try: 72 | curr_lang = vim.current.buffer.vars['current_syntax'] 73 | except: 74 | pass 75 | 76 | """Text turned on?""" 77 | no_text = False 78 | 79 | """Create parser for args""" 80 | parser = argparse.ArgumentParser(description="Process a search query") 81 | 82 | parser.add_argument( 83 | '-l', '--language', default=curr_lang, help="Set the language explicitly") 84 | parser.add_argument( 85 | '-n', '--no-text', action='store_true', default=False, 86 | help="Don't print the answer text") 87 | parser.add_argument('search', nargs='+', help="The search keywords") 88 | 89 | """Parse!""" 90 | gso_command = vars(parser.parse_args(all_args)) 91 | 92 | curr_lang = gso_command['language'].lower() 93 | no_text = gso_command['no_text'] 94 | question = gso_command['search'] 95 | 96 | """Now all the options are loaded""" 97 | 98 | starting_line = vim.current.window.cursor[0] 99 | current_line = starting_line 100 | 101 | results = [] 102 | i = 0 103 | 104 | no_language_setting = ['none', 'nothing', 'no'] 105 | # Should we search it with a different name? 106 | search_lang = curr_lang 107 | if curr_lang in search_mapping: 108 | search_lang = search_mapping[curr_lang] 109 | elif curr_lang in no_language_setting: 110 | search_lang = "" 111 | 112 | for result in load_up_questions(str(question), search_lang): 113 | results.append(result) 114 | i += 1 115 | if i > 0: 116 | break 117 | 118 | question_url = results[0][0] 119 | answers = load_up_answers(question_url) 120 | 121 | def wrap_with_root_tag(xml_string): 122 | xml_string = u""+xml_string+u"" 123 | return xml_string 124 | 125 | parser = etree.XMLParser(recover=True) 126 | root = etree.parse( 127 | BytesIO(wrap_with_root_tag(answers[0][1]).encode('utf-8')), 128 | parser=parser) 129 | 130 | 131 | # Inside a code block 132 | inside_pre_tag = False 133 | # Inside a comment block 134 | inside_comment = False 135 | 136 | block_comments_enabled = False 137 | if curr_lang in comments and not isinstance(comments[curr_lang], str): 138 | block_comments_enabled = True 139 | 140 | #Mark the start of input 141 | if block_comments_enabled: 142 | vim.current.buffer.append( 143 | comments[curr_lang][0]+"GSO>>>"+comments[curr_lang][1], 144 | current_line) 145 | elif curr_lang in comments: 146 | vim.current.buffer.append( 147 | comments[curr_lang]+"GSO>>>", 148 | current_line) 149 | else: 150 | vim.current.buffer.append( 151 | "GSO>>>", current_line) 152 | 153 | for elem in root.iter(): 154 | known_tags = [ 155 | 'pre', 'code', 'p', 'kbd', 156 | 'a', 'li', 'em', 'ol', 'strong' 157 | ] 158 | if elem.tag not in known_tags: 159 | continue 160 | inline_tags = [ 161 | 'code', 'kbd', 'a', 'em', 'strong' 162 | ] 163 | 164 | if elem.tag == 'pre': 165 | inside_pre_tag = True 166 | elif not inside_pre_tag and no_text: 167 | """No printing out text of answer""" 168 | continue 169 | 170 | if inside_comment == False and inside_pre_tag == False: 171 | """Start a block comment""" 172 | if block_comments_enabled: 173 | vim.current.buffer[current_line] += comments[curr_lang][0] 174 | inside_comment = True 175 | if inside_comment == True and inside_pre_tag == True: 176 | """End a block comment""" 177 | if block_comments_enabled: 178 | vim.current.buffer.append( 179 | comments[curr_lang][1], current_line+1) 180 | current_line += 1 181 | inside_comment = False 182 | 183 | if elem.tag not in inline_tags: 184 | if curr_lang in comments and not block_comments_enabled and not inside_pre_tag: 185 | """Do a single line comment""" 186 | vim.current.buffer.append('', current_line+1) 187 | vim.current.buffer[current_line+1] += comments[curr_lang] 188 | else: 189 | vim.current.buffer.append('', current_line+1) 190 | current_line += 1 191 | 192 | text = "" 193 | tail = "" 194 | try: 195 | text = str(elem.text) 196 | except AttributeError: 197 | text = "" 198 | pass 199 | try: 200 | tail = str(elem.tail) 201 | except AttributeError: 202 | tail = "" 203 | pass 204 | 205 | for line in text.split('\n'): 206 | if line != "None": 207 | vim.current.buffer[current_line] += line 208 | if elem.tag == 'code' and inside_pre_tag == True: 209 | vim.current.buffer.append('', current_line+1) 210 | current_line += 1 211 | 212 | 213 | for line in str(tail).split('\n'): #213 = 186 214 | if line != "None": 215 | vim.current.buffer[current_line] += line 216 | if elem.tag == 'code' and inside_pre_tag == True: 217 | inside_pre_tag = False 218 | 219 | 220 | 221 | if inside_comment == True: 222 | if block_comments_enabled: 223 | vim.current.buffer.append( 224 | comments[curr_lang][1], current_line+1) 225 | current_line += 1 226 | inside_comment = False 227 | 228 | #Mark the end of input 229 | if block_comments_enabled: 230 | vim.current.buffer.append( 231 | comments[curr_lang][0]+"<<) 254 | -------------------------------------------------------------------------------- /plugin/gso/__init__.py: -------------------------------------------------------------------------------- 1 | """ Initiate the python module 2 | """ 3 | 4 | from gso.search_google import search_google 5 | from gso.load_up_answers import load_up_answers, load_up_questions 6 | -------------------------------------------------------------------------------- /plugin/gso/load_up_answers.py: -------------------------------------------------------------------------------- 1 | """ This file organizes the different answers from each query result to load 2 | """ 3 | 4 | import os 5 | 6 | import stackexchange 7 | from pprint import pprint 8 | from gso import search_google 9 | 10 | SE_KEY = os.environ["SE_KEY"] 11 | 12 | so = stackexchange.Site( 13 | stackexchange.StackOverflow, 14 | app_key=SE_KEY) 15 | 16 | so_superuser = stackexchange.Site( 17 | stackexchange.SuperUser, 18 | app_key=SE_KEY) 19 | 20 | so_unix = stackexchange.Site( 21 | stackexchange.UnixampLinux, 22 | app_key=SE_KEY) 23 | 24 | so_tex = stackexchange.Site( 25 | stackexchange.TeXLaTeX, 26 | app_key=SE_KEY) 27 | 28 | for site in [so, so_superuser, so_unix, so_tex]: 29 | site.impose_throttling = True 30 | site.throttle_stop = False 31 | 32 | def load_up_questions(question, language='', answers=5): 33 | """ Load up stack overflow questions from a query 34 | 35 | Args: 36 | 37 | question: String question, what you would google for 38 | 39 | language: (optional) specific language inferred from filename 40 | 41 | answers: (optional, default 5) the number of answers to load 42 | 43 | Yields: 44 | 45 | lists of strings: 46 | 0: URL to question 47 | """ 48 | query = search_google(question, language=language) 49 | results = query[u'items'] 50 | for result in results: 51 | url = result[u'link'] 52 | yield [url] 53 | 54 | def load_up_answers(URL): 55 | """ Load answers from a stack overflow URL 56 | 57 | Args: 58 | 59 | URL: string of the url for the question (https:...) 60 | 61 | Returns: 62 | 63 | List of [score, body], where the body is html 64 | """ 65 | 66 | split_url = URL.split('/') 67 | domain = split_url[2] 68 | site = { 69 | 'unix.stackexchange.com': so_unix, 70 | 'tex.stackexchange.com': so_tex, 71 | 'superuser.com': so_superuser, 72 | 'stackoverflow.com': so}[domain] 73 | question_id = split_url[4] 74 | answer_pointers = site.question(question_id).answers 75 | 76 | answer_pointers.sort(key=lambda x: x.score) 77 | answer_pointers = list(reversed(answer_pointers)) 78 | 79 | answer_ids = [answer.id for answer in answer_pointers] 80 | # Only look at the first answer 81 | answer_ids = [answer_ids[0]] 82 | 83 | answer_objs = [ 84 | site.answer( 85 | answer_id, 86 | body=True, 87 | score=True) for answer_id in answer_ids] 88 | 89 | answers = [ 90 | [answer.score, 91 | answer.body] for answer in answer_objs] 92 | 93 | return answers 94 | 95 | -------------------------------------------------------------------------------- /plugin/gso/search_google.py: -------------------------------------------------------------------------------- 1 | """ This file defines the base query to Google. 2 | """ 3 | import pprint 4 | import os 5 | from googleapiclient.discovery import build 6 | 7 | my_api_key = os.environ["GOOGLE_KEY"] 8 | 9 | # A Custom Search Engine for StackOverflow, SuperUsers, TeX, and 10 | # Unix/Linux 11 | my_cse_id = "003962226882031433174:qk7rs-ca-bi" 12 | 13 | def search_google(question, language=''): 14 | """ Search google with a question, for a language (optional) 15 | 16 | Args: 17 | 18 | question: String question, what you would google for 19 | 20 | language: (optional) specific language inferred from filename 21 | 22 | Returns: 23 | 24 | res: JSON of the query result 25 | """ 26 | service = build("customsearch", "v1", 27 | developerKey=my_api_key) 28 | 29 | res = service.cse().list( 30 | q=question + ' ' + language, 31 | cx=my_cse_id, 32 | ).execute() 33 | return res 34 | -------------------------------------------------------------------------------- /tools/gso.sh: -------------------------------------------------------------------------------- 1 | vim +"GSO $*" -c "wq! /tmp/gso_output" && \ 2 | cat /tmp/gso_output && \ 3 | rm /tmp/gso_output 4 | --------------------------------------------------------------------------------