Skip to content

Commit e421902

Browse files
committed
new features: browse other users' gists; fork and star gists
1 parent 65db1c2 commit e421902

File tree

1 file changed

+156
-56
lines changed

1 file changed

+156
-56
lines changed

gist.el

Lines changed: 156 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
(require 'eieio-base)
4343
(require 'timezone)
4444

45+
(require 'gh-api)
4546
(require 'gh-gist)
4647
(require 'gh-profile)
4748

@@ -130,14 +131,21 @@ appropriate modes from fetched gist files (based on filenames)."
130131
:type '(alist :key-type (symbol :tag "Mode")
131132
:value-type (string :tag "Extension")))
132133

133-
(defvar gist-list-db nil)
134+
(defvar gist-list-db (make-hash-table :test 'equal))
135+
(defvar gist-list-db-by-user (make-hash-table :test 'equal))
134136

135137
(defvar gist-id nil)
136138
(make-variable-buffer-local 'gist-id)
137139

138140
(defvar gist-filename nil)
139141
(make-variable-buffer-local 'gist-filename)
140142

143+
(defvar gist-user-history nil "History list for gist-list-user.")
144+
145+
(defvar gist-list-buffer-user nil "Username for this gist buffer.")
146+
(make-variable-buffer-local 'gist-list-buffer-user)
147+
(put 'gist-list-buffer-user 'permanent-local t)
148+
141149
(defun gist-get-api (&optional sync)
142150
(let ((gh-profile-current-profile
143151
(or gh-profile-current-profile (gh-profile-completing-read))))
@@ -195,7 +203,7 @@ With a prefix argument, makes a private paste."
195203

196204
(defun gist-created-callback (gist)
197205
(let ((location (oref gist :html-url)))
198-
(gist-list-reload t)
206+
(gist-list-reload "" t)
199207
(message "Paste created: %s" location)
200208
(when gist-view-gist
201209
(browse-url location))
@@ -249,25 +257,46 @@ Copies the URL into the kill ring."
249257
(mark-inactive (gist-buffer-private))))
250258

251259
;;;###autoload
252-
(defun gist-list (&optional force-reload background)
253-
"Displays a list of all of the current user's gists in a new buffer."
254-
(interactive "P")
260+
(defun gist-list-user (username &optional force-reload background)
261+
"Displays a list of a user's gists in a new buffer. When called from
262+
a program, pass a blank string as the username to view the user's own
263+
gists, or nil for the username and a non-nil value for force-reload to
264+
reload the gists for the current buffer."
265+
(interactive
266+
(let ((username (read-from-minibuffer "GitHub user: " nil nil nil
267+
'gist-user-history))
268+
(force-reload (equal current-prefix-arg '(4))))
269+
(list username force-reload)))
255270
;; if buffer exists, it contains the current gh profile
256271
(let* ((gh-profile-current-profile (or gh-profile-current-profile
257272
(gh-profile-completing-read)))
258-
(bufname (format "*%s:gists*" gh-profile-current-profile))
259-
(api (gist-get-api nil)))
273+
(bufname (if (null username)
274+
(if (not (equal major-mode 'gist-list-mode))
275+
(error "Current buffer isn't a gist-list-mode buffer")
276+
(buffer-name))
277+
(format "*%s:%sgists*"
278+
gh-profile-current-profile
279+
(if (string= "" username)
280+
""
281+
(format "%s's-" username)))))
282+
(api (gist-get-api nil))
283+
(username (or (and (null username) gist-list-buffer-user)
284+
(and (not (or (null username)
285+
(string= "" username)))
286+
username)
287+
(gh-api-get-username api))))
260288
(when force-reload
261289
(pcache-clear (oref api :cache))
262-
(or background (message "Retrieving list of your gists...")))
290+
(or background (message "Retrieving list of gists...")))
263291
(unless (and background (not (get-buffer bufname)))
264-
(let ((resp (gh-gist-list api)))
292+
(let ((resp (gh-gist-list api username)))
265293
(gh-url-add-response-callback
266294
resp
267295
(lexical-let ((buffer bufname))
268296
(lambda (gists)
269297
(with-current-buffer (get-buffer-create buffer)
270-
(gist-lists-retrieved-callback gists background)))))
298+
(setq gist-list-buffer-user username)
299+
(gist-lists-retrieved-callback gists username background)))))
271300
(gh-url-add-response-callback
272301
resp
273302
(lexical-let ((profile (oref api :profile))
@@ -276,20 +305,30 @@ Copies the URL into the kill ring."
276305
(with-current-buffer buffer
277306
(setq gh-profile-current-profile profile)))))))))
278307

279-
(defun gist-list-reload (&optional background)
308+
;;;###autoload
309+
(defun gist-list (&optional force-reload background)
310+
"Displays a list of all of the current user's gists in a new buffer."
311+
(interactive "P")
312+
(gist-list-user "" force-reload background))
313+
314+
(defun gist-list-reload (&optional username background)
280315
(interactive)
281-
(gist-list t background))
316+
(gist-list-user username t background))
282317

283318
(defun gist-tabulated-entry (gist)
284319
(let* ((data (gist-parse-gist gist))
285320
(repo (oref gist :id)))
286321
(list repo (apply 'vector data))))
287322

288-
(defun gist-lists-retrieved-callback (gists &optional background)
323+
(defun gist-lists-retrieved-callback (gists username &optional background)
289324
"Called when the list of gists has been retrieved. Displays
290325
the list."
291-
(setq gist-list-db gists)
292-
(gist-list-render background))
326+
(dolist (g (gethash username gist-list-db-by-user))
327+
(remhash (oref g :id) gist-list-db))
328+
(dolist (g gists)
329+
(puthash (oref g :id) g gist-list-db))
330+
(puthash username gists gist-list-db-by-user)
331+
(gist-list-render (gethash username gist-list-db-by-user) background))
293332

294333
(defun gist--get-time (gist)
295334
(let* ((date (timezone-parse-date (oref gist :date)))
@@ -330,15 +369,18 @@ for the gist."
330369
(interactive "sGist ID: ")
331370
(let ((gist nil)
332371
(multi nil)
333-
(prefix (format "*gist %s*" id))
372+
(prefix (format "*gist-%s*" id))
334373
(result nil)
335374
(profile (gh-profile-current-profile)))
336375
(setq gist (gist-list-db-get-gist id))
337376
(let ((api (gist-get-api t)))
338377
(cond ((null gist)
339378
;; fetch it
340379
(setq gist (oref (gh-gist-get api id) :data))
341-
(add-to-list 'gist-list-db gist))
380+
(puthash (oref gist :id) gist gist-list-db)
381+
(let* ((user (oref gist :user))
382+
(gists (push gist (gethash user gist-list-db-by-user))))
383+
(puthash user gists gist-list-db-by-user)))
342384
((not (gh-gist-gist-has-files gist))
343385
(gh-gist-get api gist))))
344386
(let ((files (oref gist :files)))
@@ -386,16 +428,25 @@ for the gist."
386428
(gist-fetch-current)
387429
(select-window win)))
388430

431+
(defun gist--check-perms-and-get-api (gist errormsg apiflg)
432+
(let* ((api (gist-get-api t))
433+
(username (gh-api-get-username api))
434+
(gs (gethash username gist-list-db-by-user)))
435+
(if (not (memq gist gs))
436+
(user-error errormsg)
437+
api)))
438+
389439
(defun gist-edit-current-description ()
390440
(interactive)
391441
(let* ((id (tabulated-list-get-id))
392442
(gist (gist-list-db-get-gist id))
393-
(old-descr (oref gist :description))
394-
(new-descr (read-from-minibuffer "Description: " old-descr)))
395-
(let* ((g (clone gist
443+
(api (gist--check-perms-and-get-api
444+
gist "Can't edit a gist that doesn't belong to you" t)))
445+
(let* ((old-descr (oref gist :description))
446+
(new-descr (read-from-minibuffer "Description: " old-descr))
447+
(g (clone gist
396448
:files nil
397449
:description new-descr))
398-
(api (gist-get-api t))
399450
(resp (gh-gist-edit api g)))
400451
(gh-url-add-response-callback resp
401452
(lambda (gist)
@@ -406,18 +457,20 @@ for the gist."
406457
(let* ((buffer (get-buffer buffer))
407458
(id (tabulated-list-get-id))
408459
(gist (gist-list-db-get-gist id))
409-
(fname (file-name-nondirectory (or (buffer-file-name buffer) (buffer-name buffer)))))
410-
(let* ((g (clone gist :files
411-
(list
412-
(gh-gist-gist-file "file"
413-
:filename fname
414-
:content (with-current-buffer buffer
415-
(buffer-string ))))))
416-
(api (gist-get-api t))
417-
(resp (gh-gist-edit api g)))
418-
(gh-url-add-response-callback resp
419-
(lambda (gist)
420-
(gist-list-reload))))))
460+
(api (gist--check-perms-and-get-api
461+
gist "Can't modify a gist that doesn't belong to you" t))
462+
(fname (file-name-nondirectory (or (buffer-file-name buffer)
463+
(buffer-name buffer))))
464+
(g (clone gist :files
465+
(list
466+
(gh-gist-gist-file "file"
467+
:filename fname
468+
:content (with-current-buffer buffer
469+
(buffer-string))))))
470+
(resp (gh-gist-edit api g)))
471+
(gh-url-add-response-callback resp
472+
(lambda (gist)
473+
(gist-list-reload)))))
421474

422475
(defun gist-remove-file (fname)
423476
(interactive (list
@@ -428,24 +481,26 @@ for the gist."
428481
(mapcar #'(lambda (f) (oref f :filename))
429482
(oref gist :files))))))
430483
(let* ((id (tabulated-list-get-id))
431-
(gist (gist-list-db-get-gist id)))
432-
(let* ((g (clone gist :files
433-
(list
434-
(gh-gist-gist-file "file"
435-
:filename fname
436-
:content nil))))
437-
(api (gist-get-api t))
438-
(resp (gh-gist-edit api g)))
439-
(gh-url-add-response-callback resp
440-
(lambda (gist)
441-
(gist-list-reload))))))
484+
(gist (gist-list-db-get-gist id))
485+
(api (gist--check-perms-and-get-api
486+
gist "Can't modify a gist that doesn't belong to you" t))
487+
(g (clone gist :files
488+
(list
489+
(gh-gist-gist-file "file"
490+
:filename fname
491+
:content nil))))
492+
(resp (gh-gist-edit api g)))
493+
(gh-url-add-response-callback resp
494+
(lambda (gist)
495+
(gist-list-reload)))))
442496

443497
(defun gist-kill-current ()
444-
(interactive)
445-
(let ((id (tabulated-list-get-id)))
498+
(let* ((id (tabulated-list-get-id))
499+
(gist (gist-list-db-get-gist id))
500+
(api (gist--check-perms-and-get-api
501+
gist "Can't delete a gist that doesn't belong to you" t)))
446502
(when (yes-or-no-p (format "Really delete gist %s ? " id) )
447-
(let* ((api (gist-get-api t))
448-
(resp (gh-gist-delete api id)))
503+
(let* ((resp (gh-gist-delete api id)))
449504
(gist-list-reload)))))
450505

451506
(defun gist-current-url ()
@@ -465,6 +520,49 @@ put it into `kill-ring'."
465520
(interactive)
466521
(browse-url (gist-current-url)))
467522

523+
(defun gist--do-star (id how msg)
524+
(let* ((api (gist-get-api t))
525+
(resp (gh-gist-set-star api id how)))
526+
(gh-url-add-response-callback resp
527+
(lambda (gist)
528+
(message msg id)))))
529+
530+
;;;###autoload
531+
(defun gist-star ()
532+
(interactive)
533+
(let ((id (tabulated-list-get-id)))
534+
(gist--do-star id t "Starred gist %s")))
535+
536+
;;;###autoload
537+
(defun gist-unstar ()
538+
(interactive)
539+
(let ((id (tabulated-list-get-id)))
540+
(gist--do-star id nil "Unstarred gist %s")))
541+
542+
;;;###autoload
543+
(defun gist-list-starred (&optional background)
544+
"List your starred gists."
545+
(interactive)
546+
(let* ((api (gist-get-api t))
547+
(resp (gh-gist-list-starred api)))
548+
(gh-url-add-response-callback
549+
resp
550+
(lexical-let ((buffer "*starred-gists*"))
551+
(lambda (gists)
552+
(with-current-buffer (get-buffer-create buffer)
553+
(gist-list-render gists background)))))))
554+
555+
;;;###autoload
556+
(defun gist-fork ()
557+
"Fork a gist."
558+
(interactive)
559+
(let* ((id (tabulated-list-get-id))
560+
(api (gist-get-api))
561+
(resp (gh-gist-fork api id)))
562+
(gh-url-add-response-callback resp
563+
(lambda (gist)
564+
(message "Forked gist %s" id)))))
565+
468566
(defvar gist-list-menu-mode-map
469567
(let ((map (make-sparse-keymap)))
470568
(set-keymap-parent map tabulated-list-mode-map)
@@ -477,6 +575,9 @@ put it into `kill-ring'."
477575
(define-key map "-" 'gist-remove-file)
478576
(define-key map "y" 'gist-print-current-url)
479577
(define-key map "b" 'gist-browse-current-url)
578+
(define-key map "*" 'gist-star)
579+
(define-key map "^" 'gist-unstar)
580+
(define-key map "f" 'gist-fork)
480581
map))
481582

482583
(define-derived-mode gist-list-mode tabulated-list-mode "Gist Menu"
@@ -492,20 +593,20 @@ put it into `kill-ring'."
492593
(tabulated-list-init-header)
493594
(use-local-map gist-list-menu-mode-map))
494595

495-
(defun gist-list-render (&optional background)
596+
(defun gist-list-render (gists &optional background)
496597
(gist-list-mode)
497-
(setq tabulated-list-entries
498-
(mapcar 'gist-tabulated-entry gist-list-db))
598+
(setq tabulated-list-entries (mapcar 'gist-tabulated-entry gists))
499599
(tabulated-list-print)
500600
(gist-list-tag-multi-files)
501601
(unless background
502602
(set-window-buffer nil (current-buffer))))
503603

504604
(defun gist-list-tag-multi-files ()
505605
(let ((ids nil))
506-
(dolist (gist gist-list-db)
507-
(when (< 1 (length (oref gist :files)))
508-
(push (oref gist :id) ids)))
606+
(maphash (lambda (k v)
607+
(when (< 1 (length (oref v :files)))
608+
(push (oref v :id) ids)))
609+
gist-list-db)
509610
(save-excursion
510611
(goto-char (point-min))
511612
(while (not (eobp))
@@ -514,8 +615,7 @@ put it into `kill-ring'."
514615
(forward-line 1))))))
515616

516617
(defun gist-list-db-get-gist (id)
517-
(loop for gist in gist-list-db if (string= (oref gist :id) id)
518-
return gist))
618+
(gethash id gist-list-db))
519619

520620
;;; Gist minor mode
521621

0 commit comments

Comments
 (0)