vnews 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ .DS_Store
2
+ *swp
3
+ login.yml
4
+ gmail.yml
5
+ *.log
6
+ pkg/
7
+ .rvmrc
8
+ notes.txt
9
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+ gem 'nokogiri'
3
+ gem 'feed_yamlizer', '>= 0.0.3'
4
+ gem 'mysql2'
data/Gemfile.lock ADDED
@@ -0,0 +1,19 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ feed_yamlizer (0.0.2)
5
+ htmlentities
6
+ nokogiri
7
+ sqlite3-ruby
8
+ htmlentities (4.2.4)
9
+ nokogiri (1.4.4)
10
+ sqlite3 (1.3.3)
11
+ sqlite3-ruby (1.3.3)
12
+ sqlite3 (>= 1.3.3)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ feed_yamlizer
19
+ nokogiri
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2010 Daniel Choi, http://danielchoi.com/software/
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
2
+ require 'couchrest'
3
+ require 'yaml'
4
+ require 'json'
5
+ require 'vnews'
6
+ require 'rake'
7
+ require 'rake/testtask'
8
+ require 'bundler'
9
+ Bundler::GemHelper.install_tasks
10
+
11
+ desc "Save couchdb views in lib/couchviews.yml"
12
+ task :create_views do
13
+ db = CouchRest.database! "http://localhost:5984/vnews"
14
+ views = YAML::load File.read("lib/couchviews.yml")
15
+ begin
16
+ rev = db.get(views['_id'])['_rev']
17
+ puts db.save_doc(views.merge('_rev' => rev))
18
+ rescue RestClient::ResourceNotFound
19
+ puts db.save_doc(views)
20
+ end
21
+ end
22
+
23
+ desc "List feeds with recent entry titles"
24
+ task :list_feeds do
25
+ vnews = Vnews::Aggregator.new
26
+ puts vnews.list_feeds.inspect
27
+ end
28
+
29
+ desc "Run tests"
30
+ task :test do
31
+ $:.unshift File.expand_path("test")
32
+ MiniTest::Unit.autorun
33
+ end
34
+
35
+ task :default => :test
36
+
37
+
data/bin/vnews-client ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # for dev only; comment out for production
4
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
5
+
6
+ require 'vnews/display'
7
+
8
+ case ARGV.first
9
+ when 'update_feed'
10
+ require 'vnews/feed'
11
+ puts Vnews::Feed.update_feed ARGV[1]
12
+ when 'update_folder'
13
+ require 'vnews/folder'
14
+ puts Vnews::Folder.update_folder ARGV[1]
15
+ else
16
+ puts Vnews::Display.new.send *ARGV
17
+ end
18
+
19
+
data/lib/create.sql ADDED
@@ -0,0 +1,36 @@
1
+ create table feeds (
2
+ title varchar(255),
3
+ feed_url varchar(255),
4
+ link varchar(255),
5
+ num_items_read int default 0, /*a counter of the number of items read from this feed*/
6
+ last_viewed_at datetime,
7
+ primary key (feed_url)
8
+ ) type=MyISAM;
9
+
10
+ create table items (
11
+ guid varchar(255),
12
+ feed varchar(255), /* feed link is the foreign key */
13
+ feed_title varchar(255), /*denorm a little*/
14
+ title varchar(255),
15
+ link varchar(255),
16
+ pub_date datetime,
17
+ author varchar(255),
18
+ text text,
19
+ word_count int unsigned,
20
+ unread bool default true,
21
+ starred bool default false,
22
+ primary key (feed,guid),
23
+ fulltext (title, text)
24
+ ) type=MyISAM;
25
+
26
+ alter table items add index feed (feed(5));
27
+
28
+ create table feeds_folders (
29
+ feed varchar(255), /* the feed link */
30
+ folder varchar(255), /* folder title */
31
+ last_viewed_at datetime,
32
+ unique key (feed, folder)
33
+ ) type=MyISAM;
34
+
35
+ alter table feeds_folders add index folder (folder(5));
36
+
data/lib/vnews.rb ADDED
@@ -0,0 +1,63 @@
1
+ require 'vnews/version'
2
+ require 'vnews/config'
3
+ require 'vnews/feed'
4
+ require 'logger'
5
+ require 'drb'
6
+
7
+ class Vnews
8
+
9
+ def self.start
10
+ puts "Starting vnews #{Vnews::VERSION}"
11
+
12
+ if ! File.exists?(File.expand_path(Vnews::Config::CONFIGPATH))
13
+ puts "Missing #{Vnews::Config::CONFIGPATH}"
14
+ # generate this file
15
+ puts "Generating stub config file at #{Vnews::Config::CONFIGPATH}"
16
+ File.open(Vnews::Config::CONFIGPATH, 'w') {|f| f.write(Config.stub_config)}
17
+ puts "Please edit this file and then run `vnews --create-db` to create your Vnews MySQL database."
18
+ exit
19
+ end
20
+
21
+ if ARGV.first == "--create-db"
22
+ c = File.read(Vnews::Config::CONFIGPATH)
23
+ top, bottom = c.split(/^\s*$/,2)
24
+ dbconfig = YAML::load(top)
25
+ puts "Creating database: #{dbconfig['database']}"
26
+ Vnews::Sql.create_db dbconfig
27
+ puts "OK if everything went ok, you can create your feeds and folders with `vnews -u`."
28
+ exit
29
+ end
30
+
31
+ if ARGV.first == "--opml"
32
+ require 'vnews/opml'
33
+ # opml file must be second arg
34
+ puts "Importing OPML file #{ARGV[1]}"
35
+ Vnews::Opml.import File.read(ARGV[1])
36
+ # rewrite .vnewsrc config
37
+ puts "Rewriting config file #{Vnews::Config::CONFIGPATH} to reflect changes."
38
+ Vnews::Config.rewrite_config
39
+ puts "Done."
40
+ end
41
+
42
+ if ['--update', '-u'].include?(ARGV.first)
43
+ Vnews::Config.update_folders
44
+ end
45
+
46
+ Vnews.sql_client # loads the config
47
+
48
+ vim = ENV['VMAIL_VIM'] || 'vim'
49
+ vimscript = File.join(File.dirname(__FILE__), "vnews.vim")
50
+ vim_command = "#{vim} -S #{vimscript} "
51
+ STDERR.puts vim_command
52
+ system(vim_command)
53
+ if vim == 'mvim'
54
+ DRb.thread.join
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+
61
+ if __FILE__ == $0
62
+ Vnews.start
63
+ end
data/lib/vnews.vim ADDED
@@ -0,0 +1,472 @@
1
+ " Vim script that turns Vim into a feed reader
2
+ " Maintainer: Daniel Choi <dhchoi@gnews.com>
3
+ " License: MIT License (c) 2011 Daniel Choi
4
+
5
+ if exists("g:VnewsLoaded") || &cp || version < 700
6
+ finish
7
+ endif
8
+ let g:VnewsLoaded = 1
9
+
10
+ let mapleader = ','
11
+ highlight VnewsSearchTerm ctermbg=green guibg=green
12
+
13
+
14
+ "let s:client_script = 'vnews-client '
15
+ let s:client_script = 'bin/vnews-client '
16
+ let s:list_folders_command = s:client_script . 'folders '
17
+ let s:list_feeds_command = s:client_script . 'feeds '
18
+ let s:list_folder_items_command = s:client_script . 'folder_items '
19
+ let s:list_feed_items_command = s:client_script . 'feed_items '
20
+ let s:show_item_command = s:client_script . 'show_item '
21
+ let s:star_item_command = s:client_script . 'star_item ' " + guid star(bool)
22
+ let s:unstar_item_command = s:client_script . 'unstar_item ' " + guid star(bool)
23
+ let s:delete_item_command = s:client_script . 'delete_item ' " + guid
24
+ let s:search_items_command = s:client_script . 'search_items '
25
+ let s:cat_items_command = s:client_script . 'cat_items '
26
+
27
+ let s:folder = "All"
28
+ let s:feed = "All"
29
+ function! VnewsStatusLine()
30
+ let end_index = match(s:last_selection, '(\d\+)$')
31
+ let selection = s:last_selection[0:end_index-1]
32
+ return "%<%f\ " . s:selectiontype . " " . selection . "%r%=%-14.(%l,%c%V%)\ %P"
33
+ endfunction
34
+
35
+ func! s:trimString(string)
36
+ let string = substitute(a:string, '\s\+$', '', '')
37
+ return substitute(string, '^\s\+', '', '')
38
+ endfunc
39
+
40
+
41
+ function! s:common_mappings()
42
+ nnoremap <silent> <buffer> <Space> :call <SID>toggle_maximize_window()<cr>
43
+ nnoremap <buffer> <leader>n :call <SID>list_folders()<CR>
44
+ nnoremap <buffer> <leader>m :call <SID>list_feeds(0)<CR>
45
+ nnoremap <buffer> <leader>M :call <SID>list_feeds(1)<CR>
46
+ nnoremap <buffer> <leader>* :call <SID>toggle_star()<CR>
47
+ nnoremap <buffer> <leader>8 :call <SID>toggle_star()<CR>
48
+ nnoremap <buffer> <leader># :call <SID>delete_item()<CR>
49
+ nnoremap <buffer> <leader>3 :call <SID>delete_item()<CR>
50
+ nnoremap <buffer> u :call <SID>update_feed()<CR>
51
+ nnoremap <buffer> <leader>u :call <SID>update_feed()<CR>
52
+ command! -bar -nargs=0 VNUpdateFeed :call <SID>update_feed()
53
+ endfunc
54
+
55
+ function! s:create_list_window()
56
+ new list-window
57
+ wincmd p
58
+ close
59
+ setlocal bufhidden=hide
60
+ setlocal buftype=nofile
61
+ setlocal modifiable
62
+ setlocal textwidth=0
63
+ setlocal nowrap
64
+ setlocal number
65
+ setlocal foldcolumn=0
66
+ setlocal nospell
67
+ " setlocal noreadonly
68
+ " hi CursorLine cterm=NONE ctermbg=darkred ctermfg=white guibg=darkred guifg=white
69
+ setlocal cursorline
70
+ " we need to find the window later
71
+ let s:listbufnr = bufnr('')
72
+ let s:listbufname = bufname('')
73
+ setlocal statusline=%!VnewsStatusLine()
74
+ nnoremap <silent> <buffer> <cr> :call <SID>show_item_under_cursor(1)<CR>
75
+ nnoremap <silent> <buffer> <c-l> :call <SID>show_item_under_cursor(1)<CR>:wincmd p<CR>
76
+ nnoremap <silent> <buffer> <c-j> :call <SID>show_adjacent_item(0, 'list-window')<CR>
77
+ nnoremap <silent> <buffer> <c-k> :call <SID>show_adjacent_item(1, 'list-window')<CR>
78
+ command! -bar -nargs=0 -range VNDelete :<line1>,<line2>call s:delete_item()
79
+ command! -bar -nargs=0 -range VNConcat :<line1>,<line2>call s:cat_items()
80
+ call s:common_mappings()
81
+ if !exists("g:VnewsStarredColor")
82
+ let g:VnewsStarredColor = "ctermfg=green guifg=green guibg=grey"
83
+ endif
84
+ syn match VnewsBufferStarred /^*.*/hs=s
85
+ exec "hi def VnewsBufferStarred " . g:VnewsStarredColor
86
+ endfunction
87
+
88
+ function! s:create_item_window()
89
+ rightbelow split item-window
90
+ setlocal modifiable
91
+ setlocal buftype=nofile
92
+ let s:itembufnr = bufnr('%')
93
+ nnoremap <silent> <buffer> <cr> <C-W>=<C-W>p
94
+ nnoremap <silent> <buffer> <c-j> :call <SID>show_adjacent_item(0, "item-window")<CR>
95
+ nnoremap <silent> <buffer> <c-k> :call <SID>show_adjacent_item(1, "item-window")<CR>
96
+ nnoremap <silent> <buffer> q :call <SID>close_item_window()<cr>
97
+ nnoremap <buffer> <leader>o :call <SID>find_next_href_and_open()<CR>
98
+ " opens the linked item
99
+ nnoremap <buffer> <leader>h :normal Gkk<CR>:call <SID>find_next_href_and_open()<CR>
100
+ call s:common_mappings()
101
+ close
102
+ endfunction
103
+
104
+ function! s:focus_window(target_bufnr)
105
+ if bufwinnr(a:target_bufnr) == winnr()
106
+ return
107
+ end
108
+ let winnr = bufwinnr(a:target_bufnr)
109
+ if winnr == -1
110
+ if a:target_bufnr == s:listbufnr
111
+ leftabove split
112
+ else
113
+ rightbelow split
114
+ endif
115
+ exec "buffer" . a:target_bufnr
116
+ else
117
+ exec winnr . "wincmd w"
118
+ endif
119
+ " set up syntax highlighting
120
+ if has("syntax")
121
+ "
122
+ endif
123
+ endfunction
124
+
125
+ function! s:open_selection_window(selectionlist, buffer_name, prompt)
126
+ let s:selectionlist = a:selectionlist
127
+ call s:focus_window(s:listbufnr)
128
+ exec "leftabove split ".a:buffer_name
129
+ setlocal textwidth=0
130
+ setlocal completefunc=CompleteFunction
131
+ setlocal buftype=nofile
132
+ setlocal noswapfile
133
+ setlocal modifiable
134
+ resize 1
135
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>select_folder_or_feed()<CR>
136
+ noremap <buffer> q <Esc>:close<cr>
137
+ inoremap <buffer> <Esc> <Esc>:close<cr>
138
+ call setline(1, a:prompt)
139
+ let s:prompt = a:prompt
140
+ normal $
141
+ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
142
+ endfunction
143
+
144
+ function! CompleteFunction(findstart, base)
145
+ if a:findstart
146
+ let start = len(s:prompt) + 1
147
+ return start
148
+ else
149
+ let base = s:trimString(a:base)
150
+ if (base == '')
151
+ return s:selectionlist
152
+ else
153
+ let res = []
154
+ for m in s:selectionlist
155
+ if m =~ '\c' . base
156
+ call add(res, m)
157
+ endif
158
+ endfor
159
+ return res
160
+ endif
161
+ endif
162
+ endfun
163
+
164
+ " selection window pick
165
+ function! s:select_folder_or_feed()
166
+ " let folder_or_feed = s:trimString(join(split(getline(line('.')), ":")[1:-1], ":"))
167
+ let folder_or_feed = getline('.')[len(s:prompt):]
168
+ close
169
+ call s:focus_window(s:listbufnr)
170
+ if (folder_or_feed == '') " no selection
171
+ return
172
+ end
173
+ call s:fetch_items(folder_or_feed)
174
+ endfunction
175
+
176
+ func! s:list_folders()
177
+ let folders = split(system(s:list_folders_command), "\n")
178
+ if len(folders) == 0
179
+ echom "There are no folders."
180
+ else
181
+ let s:selectiontype = "folder"
182
+ call s:open_selection_window(folders, 'select-folder', "Select folder: ")
183
+ end
184
+ endfunc
185
+
186
+ func! s:list_feeds(popular_first)
187
+ " default is alphabetical
188
+ " 1 means order by popular_first
189
+ let res = system(s:list_feeds_command . " " . a:popular_first)
190
+ let promptsuffix = a:popular_first ? "(num of item views)" : "(num of unread items)"
191
+ let feeds = split(res, "\n")
192
+ if len(feeds) == 0
193
+ echom "There are no feeds."
194
+ else
195
+ let s:selectiontype = "feed"
196
+ call s:open_selection_window(feeds, 'select-feed', "Select feed ". promptsuffix .": ")
197
+ end
198
+ endfunc
199
+
200
+ func! s:display_items(res)
201
+ setlocal modifiable
202
+ silent! 1,$delete
203
+ silent! put! =a:res
204
+ silent normal Gdd
205
+ setlocal nomodifiable
206
+ normal zz
207
+ endfunc
208
+
209
+ " right now, just does folders
210
+ function! s:fetch_items(selection)
211
+ " take different actions depending on whether a feed or folder?
212
+ call s:focus_window(s:itembufnr)
213
+ call clearmatches()
214
+ call s:focus_window(s:listbufnr)
215
+ call clearmatches()
216
+ if exists("s:selectionlist") && index(s:selectionlist, a:selection) == -1
217
+ return
218
+ end
219
+ if s:selectiontype == "folder"
220
+ let command = s:list_folder_items_command
221
+ else
222
+ let command = s:list_feed_items_command
223
+ endif
224
+ let command .= winwidth(0) . ' ' .shellescape(a:selection)
225
+ let s:last_fetch_command = command " in case user later updates the feed in place
226
+ let s:last_selection = a:selection
227
+ let res = system(command)
228
+ call s:display_items(res)
229
+ normal G
230
+ call s:focus_window(s:itembufnr)
231
+ close
232
+ normal z-
233
+ " call s:show_item_under_cursor(0)
234
+ " call s:focus_window(s:listbufnr)
235
+ endfunction
236
+
237
+ func! s:get_guid(line)
238
+ let line = getline(a:line)
239
+ let s:guid = matchstr(line, '[^|]\+$')
240
+ return s:trimString(s:guid)
241
+ endfunc
242
+
243
+ "------------------------------------------------------------------------
244
+ " SHOW ITEM
245
+ " blank arg is not used yet
246
+ func! s:show_item_under_cursor(inc_read_count)
247
+ let s:guid = s:get_guid(line('.'))
248
+ if s:guid == ""
249
+ return
250
+ end
251
+ " mark as read
252
+ set modifiable
253
+ let newline = substitute(getline('.'), '^+', ' ', '')
254
+ call setline(line('.'), newline)
255
+ set nomodifiable
256
+ let res = system(s:show_item_command . shellescape( s:guid) . ' '. ( a:inc_read_count ? "1" : "" ) )
257
+ call s:focus_window(s:itembufnr)
258
+ set modifiable
259
+ silent 1,$delete
260
+ silent put =res
261
+ silent 1delete
262
+ silent normal 1Gjk
263
+ set nomodifiable
264
+ endfunc
265
+
266
+ " from message window
267
+ function! s:show_adjacent_item(up, focusbufname)
268
+ if (bufwinnr(s:listbufnr) == -1) " we're in full screen item mode
269
+ 3split " make small nav window on top
270
+ exec 'b'. s:listbufnr
271
+ else
272
+ call s:focus_window(s:listbufnr)
273
+ end
274
+ if a:up
275
+ normal k
276
+ else
277
+ normal j
278
+ endif
279
+ normal zz
280
+ call s:show_item_under_cursor(1) " TOD0 is 1 right arg?
281
+ normal zz
282
+ call s:focus_window(bufnr(a:focusbufname))
283
+ redraw
284
+ endfunction
285
+
286
+ func! s:close_item_window()
287
+ if winnr('$') > 1
288
+ close!
289
+ else
290
+ call s:focus_window(s:listbufnr)
291
+ wincmd p
292
+ close!
293
+ normal zz
294
+ endif
295
+ endfunc
296
+
297
+ func! s:toggle_maximize_window()
298
+ if bufwinnr(s:listbufnr) != -1 && bufwinnr(s:itembufnr) != -1
299
+ if bufwinnr(s:listbufnr) == winnr()
300
+ call s:focus_window(s:itembufnr)
301
+ close
302
+ else
303
+ call s:focus_window(s:listbufnr)
304
+ close
305
+ endif
306
+ elseif bufwinnr(s:listbufnr) == winnr()
307
+ call s:show_item_under_cursor(1)
308
+ elseif bufwinnr(s:itembufnr) == winnr()
309
+ call s:focus_window(s:listbufnr)
310
+ wincmd p
311
+ endif
312
+ endfunc
313
+
314
+ "------------------------------------------------------------------------
315
+ let s:http_link_pattern = 'https\?:[^ >)\]]\+'
316
+
317
+ func! s:open_href_under_cursor()
318
+ let href = expand("<cWORD>")
319
+ let command = g:Vnews#browser_command . " '" . href . "' "
320
+ call system(command)
321
+ echom command
322
+ endfunc
323
+
324
+ func! s:find_next_href_and_open()
325
+ let res = search(s:http_link_pattern, 'cw')
326
+ if res != 0
327
+ call s:open_href_under_cursor()
328
+ endif
329
+ endfunc
330
+
331
+ if !exists("g:Vnews#browser_command")
332
+ for cmd in ["gnome-open", "open"]
333
+ if executable(cmd)
334
+ let g:Vnews#browser_command = cmd
335
+ break
336
+ endif
337
+ endfor
338
+ if !exists("g:Vnews#browser_command")
339
+ echom "Can't find the to open your web browser."
340
+ endif
341
+ endif
342
+
343
+ "------------------------------------------------------------------------
344
+ " TOGGLE STAR
345
+
346
+ function! s:toggle_star()
347
+ let original_winnr = winnr()
348
+ call s:focus_window(s:listbufnr)
349
+ let s:guid = s:get_guid(line('.'))
350
+ let flag_symbol = "^*"
351
+ if match(getline('.'), flag_symbol) != -1
352
+ let already_starred = 1
353
+ else
354
+ let already_starred = 0
355
+ end
356
+ if !already_starred
357
+ let command = s:star_item_command
358
+ else
359
+ let command = s:unstar_item_command
360
+ endif
361
+ let command .= shellescape(s:guid )
362
+ let res = system(command)
363
+ setlocal modifiable
364
+ let line = getline('.')
365
+ " toggle * on line
366
+ if !already_starred
367
+ let newline = substitute(line, '^ ', '*', '')
368
+ let newline = substitute(newline, '^+', '*', '')
369
+ else
370
+ let newline = substitute(line, '^*', ' ', '')
371
+ endif
372
+ call setline(line('.'), newline)
373
+ setlocal nomodifiable
374
+ exec original_winnr . "wincmd w"
375
+ redraw
376
+ endfunction
377
+
378
+ "------------------------------------------------------------------------
379
+ " DELETE ITEMS
380
+
381
+ func! s:delete_item()
382
+ call s:focus_window(s:listbufnr)
383
+ let uid = s:get_guid(line('.'))
384
+ let command = s:delete_item_command . shellescape(uid)
385
+ let res = system(command)
386
+ setlocal modifiable
387
+ exec "silent " . line('.') . "delete"
388
+ setlocal nomodifiable
389
+ if winnr("$") > 1
390
+ wincmd p
391
+ close
392
+ end
393
+ redraw
394
+ echom "Item ".uid." deleted"
395
+ endfunc
396
+
397
+ "------------------------------------------------------------------------
398
+ " PRINT ITEMS
399
+
400
+ " must be called from list window
401
+ func! s:cat_items() range
402
+ let lnum = a:firstline
403
+ let items = []
404
+ while lnum <= a:lastline
405
+ let guid = s:get_guid(lnum)
406
+ call add(items, shellescape(guid))
407
+ let lnum += 1
408
+ endwhile
409
+ call s:focus_window(s:itembufnr)
410
+ only
411
+ let command = s:cat_items_command . join(items, ' ')
412
+ let res = system(command)
413
+ setlocal modifiable
414
+ silent 1,$delete
415
+ silent put =res
416
+ silent 1delete
417
+ silent normal 1Gjk
418
+ setlocal nomodifiable
419
+ redraw
420
+ echom "Concatenated ".len(items)."item".(len(items) == 1 ? '' : 's')
421
+ endfunc
422
+
423
+
424
+ "------------------------------------------------------------------------
425
+ " SEARCH
426
+ func! s:search_items(term)
427
+ call clearmatches()
428
+ call s:focus_window(s:listbufnr)
429
+ let command = s:search_items_command . winwidth(0) . ' ' . shellescape(a:term)
430
+ let res = system(command)
431
+ call s:display_items(res)
432
+ " show item for top match
433
+ normal gg
434
+ call s:show_item_under_cursor(0)
435
+ call matchadd("VnewsSearchTerm", '\c' . a:term )
436
+ call s:focus_window(s:listbufnr)
437
+ call matchadd("VnewsSearchTerm", '\c' . a:term )
438
+ endfunc
439
+
440
+ "------------------------------------------------------------------------
441
+ " UPDATE FEED
442
+
443
+ func! s:update_feed()
444
+ call s:focus_window(s:listbufnr)
445
+ if exists("s:last_selection")
446
+ if s:selectiontype == "folder"
447
+ exec ":!vnews-client update_folder ".shellescape(s:last_selection)
448
+ elseif s:selectiontype == "feed"
449
+ exec ":!vnews-client update_feed ".shellescape(s:last_selection)
450
+ end
451
+ endif
452
+ if exists("s:last_fetch_command")
453
+ let res = system(s:last_fetch_command)
454
+ call s:display_items(res)
455
+ end
456
+ redraw!
457
+ normal G
458
+ call s:show_item_under_cursor(0)
459
+ redraw!
460
+ endfunc
461
+
462
+
463
+ command! -bar -nargs=1 VNSearch :call s:search_items(<f-args>)
464
+
465
+
466
+ call s:create_list_window()
467
+ call s:create_item_window()
468
+ call s:focus_window(s:listbufnr)
469
+ let s:selectiontype = "folder"
470
+ call s:fetch_items("All (0)") " number won't show but is assumed by function VnewsStatusLine()
471
+
472
+