vnews 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|