vnews 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +19 -0
- data/MIT-LICENSE.txt +21 -0
- data/Rakefile +37 -0
- data/bin/vnews-client +19 -0
- data/lib/create.sql +36 -0
- data/lib/vnews.rb +63 -0
- data/lib/vnews.vim +472 -0
- data/lib/vnews/autodiscoverer.rb +24 -0
- data/lib/vnews/config.rb +122 -0
- data/lib/vnews/display.rb +157 -0
- data/lib/vnews/exceptions.rb +4 -0
- data/lib/vnews/feed.rb +77 -0
- data/lib/vnews/folder.rb +29 -0
- data/lib/vnews/opml.rb +50 -0
- data/lib/vnews/sql.rb +190 -0
- data/lib/vnews/version.rb +4 -0
- data/recreate.sh +3 -0
- data/vnews.gemspec +27 -0
- metadata +111 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
|
+
|