Switch to side-by-side view

--- a/Allura/allura/model/repo.py
+++ b/Allura/allura/model/repo.py
@@ -17,6 +17,7 @@
 
 from allura.lib import utils
 from allura.lib import helpers as h
+from allura.lib.patience import SequenceMatcher
 
 from .auth import User
 from .session import main_doc_session, project_doc_session
@@ -231,10 +232,38 @@
     def context(self):
         result = dict(prev=None, next=None)
         if self.parent_ids:
-            result['prev'] = self.query.get(_id=self.parent_ids[0])
+            result['prev'] = self.query.find(dict(_id={'$in': self.parent_ids })).all()
         if self.child_ids:
-            result['next'] = self.query.get(_id=self.child_ids[0])
+            result['next'] = self.query.find(dict(_id={'$in': self.child_ids })).all()
         return result
+
+    @LazyProperty
+    def diffs(self):
+        di = DiffInfoDoc.m.get(_id=self._id)
+        if di is None:
+            return dict(added=[], removed=[], changed=[], copied=[])
+        added = []
+        removed = []
+        changed = []
+        copied = []
+        for change in di.differences:
+            print change.name
+            if change.rhs_id is None:
+                removed.append(change.name)
+            elif change.lhs_id is None:
+                added.append(change.name)
+            else:
+                changed.append(change.name)
+            return dict(
+                added=added, removed=removed,
+                changed=changed, copied=copied)
+
+    def get_path(self, path):
+        parts = path.split('/')[1:]
+        cur = self.tree
+        for part in parts:
+            cur = cur[part]
+        return cur
 
 class Tree(RepoObject):
     # Ephemeral attrs
@@ -258,7 +287,8 @@
 
     def __getitem__(self, name):
         obj = self.by_name[name]
-        if obj['type'] == 'blob': return obj
+        if obj['type'] == 'blob':
+            return Blob(self, name, obj['id'])
         obj = self.query.get(_id=obj['id'])
         if obj is None:
             oid = self.repo.compute_tree_new(self.commit, self.path() + name + '/')
@@ -282,13 +312,9 @@
         for x in self.blob_ids:
             if README_RE.match(x.name):
                 name = x.name
-                obj = Object(
-                    object_id=x.id,
-                    path=lambda:self.path() + x['name'],
-                    commit=Object(
-                        object_id=self.commit._id))
-                text = self.repo.open_blob(obj).read()
-                return (x.name, h.really_unicode(text))
+                blob = self[name]
+                return (x.name, h.really_unicode(blob.text))
+        return None, None
 
     def ls(self):
         # Load last commit info
@@ -302,6 +328,7 @@
         def _get_last_commit(oid):
             lc = lc_index.get(oid)
             if lc is None:
+                import pdb; pdb.set_trace()
                 lc = dict(
                     author=None,
                     author_email=None,
@@ -344,17 +371,107 @@
 
     @LazyProperty
     def by_name(self):
-        d = dict((x.name, x) for x in self.other_ids)
+        d = Object((x.name, x) for x in self.other_ids)
         d.update(
-            (x.name, dict(x, type='tree'))
+            (x.name, Object(x, type='tree'))
             for x in self.tree_ids)
         d.update(
-            (x.name, dict(x, type='blob'))
+            (x.name, Object(x, type='blob'))
             for x in self.blob_ids)
         return d
 
     def is_blob(self, name):
         return self.by_name[name]['type'] == 'blob'
+
+    def get_blob(self, name):
+        x = self.by_name[name]
+        return Blob(self, name, x.id)
+
+class Blob(object):
+    '''Lightweight object representing a file in the repo'''
+
+    def __init__(self, tree, name, _id):
+        self._id = _id
+        self.tree = tree
+        self.name = name
+        self.repo = tree.repo
+        self.commit = tree.commit
+
+    def path(self):
+        return self.tree.path() + h.really_unicode(self.name)
+
+    def url(self):
+        return self.tree.url() + h.really_unicode(self.name)
+
+    @LazyProperty
+    def prev_commit(self):
+        lc = self.repo.get_last_commit(self)
+        if lc['id']:
+            last_commit = self.repo.commit(lc.id)
+            if last_commit.parent_ids:
+                return self.repo.commit(last_commit.parent_ids[0])
+        return None
+
+    @LazyProperty
+    def next_commit(self):
+        try:
+            path = self.path()
+            cur = self.commit
+            next = cur.context()['next']
+            while next:
+                cur = next[0]
+                next = cur.context()['next']
+                other_blob = cur.get_path(path)
+                if other_blob is None or other_blob._id != self._id:
+                    return cur
+        except:
+            log.exception('Lookup prev_commit')
+            return None
+
+    @LazyProperty
+    def _content_type_encoding(self):
+        return self.repo.guess_type(self.name)
+
+    @LazyProperty
+    def content_type(self):
+        return self._content_type_encoding[0]
+
+    @LazyProperty
+    def content_encoding(self):
+        return self._content_type_encoding[1]
+
+    @property
+    def has_html_view(self):
+        return self.content_type.startswith('text/')
+
+    @property
+    def has_image_view(self):
+        return self.content_type.startswith('image/')
+
+    def context(self):
+        path = self.path()
+        prev = self.prev_commit
+        next = self.next_commit
+        if prev is not None: prev = prev.get_path(path)
+        if next is not None: next = next.get_path(path)
+        return dict(
+            prev=prev,
+            next=next)
+
+    def open(self):
+        return self.repo.open_blob(self)
+
+    def __iter__(self):
+        return iter(self.open())
+
+    @LazyProperty
+    def text(self):
+        return self.open().read()
+
+    @classmethod
+    def diff(cls, v0, v1):
+        differ = SequenceMatcher(v0, v1)
+        return differ.get_opcodes()
 
 mapper(Commit, CommitDoc, repository_orm_session)
 mapper(Tree, TreeDoc, repository_orm_session)