Skip to content

Commit

Permalink
Merge pull request #158 from tameeshB/diff-attempt-luana-fixing-inputs
Browse files Browse the repository at this point in the history
Diff feature between two texts
  • Loading branch information
chriskuehl committed Apr 25, 2023
2 parents 7fd8f83 + f7a83c3 commit d7b27c0
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 33 deletions.
31 changes: 29 additions & 2 deletions fluffy/component/highlighting.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import functools
import re
import typing
from collections import namedtuple

import pygments.lexers.teraterm
Expand Down Expand Up @@ -63,6 +64,9 @@ def name(self):
def is_terminal_output(self):
return 'ansi-color' in self.lexer.aliases

def prepare_text(self, text: str) -> typing.List[str]:
return [text]

def highlight(self, text):
text = _highlight(text, self.lexer)
return text
Expand All @@ -76,6 +80,24 @@ class DiffHighlighter(namedtuple('DiffHighlighter', ('lexer',))):
def name(self):
return f'Diff ({self.lexer.name})'

def prepare_text(self, text: str) -> typing.List[str]:
"""Transform the unified diff into a side-by-side diff."""
diff1 = []
diff2 = []
for line in text.splitlines():
if line in ('--- ', '+++ '):
pass
elif line.startswith('-'):
diff1.append(line)
diff2.append('')
elif line.startswith('+'):
diff1.append('')
diff2.append(line)
else:
diff1.append(line)
diff2.append(line)
return ['\n'.join(diff1), '\n'.join(diff2)]

def highlight(self, text):
html = pq(_highlight(text, self.lexer))
lines = html('pre > span')
Expand All @@ -87,7 +109,7 @@ def highlight(self, text):

for line in lines:
line = pq(line)
assert line.attr('id').startswith('line-')
assert line.attr('class').startswith('line-')

el = pq(line)

Expand Down Expand Up @@ -254,8 +276,13 @@ def guess_lexer(text, language, filename, opts=None):


def _highlight(text, lexer):
return pygments.highlight(
text = pygments.highlight(
text,
lexer,
_pygments_formatter,
)
# We may have multiple renders per page, but for some reason
# Pygments's HtmlFormatter only supports IDs and not classes for
# line numbers.
text = text.replace(' id="line-', ' class="line-')
return text
16 changes: 10 additions & 6 deletions fluffy/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ def debug(): # pragma: no cover

@app.route('/test/paste')
def view_paste():
text = (TESTING_DIR / 'files' / 'code.py').open().read()
highlighter = get_highlighter('', 'python', None)
return render_template(
'paste.html',
text=(TESTING_DIR / 'files' / 'code.py').open().read(),
highlighter=get_highlighter('', 'python', None),
texts=highlighter.prepare_text(text),
highlighter=highlighter,
edit_url='#edit',
raw_url='#raw',
styles=STYLES_BY_CATEGORY,
Expand All @@ -41,10 +43,11 @@ def view_paste():
@app.route('/test/diff')
def view_diff():
text = (TESTING_DIR / 'files' / 'python.diff').open().read()
highlighter = get_highlighter(text, None, None)
return render_template(
'paste.html',
text=text,
highlighter=get_highlighter(text, None, None),
texts=highlighter.prepare_text(text),
highlighter=highlighter,
edit_url='#edit',
raw_url='#raw',
styles=STYLES_BY_CATEGORY,
Expand All @@ -53,10 +56,11 @@ def view_diff():
@app.route('/test/ansi-color')
def view_ansi_color():
text = (TESTING_DIR / 'files' / 'ansi-color').open().read()
highlighter = get_highlighter(text, None, None)
return render_template(
'paste.html',
text=text,
highlighter=get_highlighter(text, None, None),
texts=highlighter.prepare_text(text),
highlighter=highlighter,
edit_url='#edit',
raw_url='#raw',
styles=STYLES_BY_CATEGORY,
Expand Down
12 changes: 12 additions & 0 deletions fluffy/static/js/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ $(document).ready(function() {
}
});

// show two text boxes for diff-between-two-texts
$("#language").change(function(e) {
const selected_lang = e.target.value;
if (selected_lang === "diff-between-two-texts") {
$("#pastedTwoText").removeClass("hidden");
$("#pastedText").addClass("hidden");
} else {
$("#pastedText").removeClass("hidden");
$("#pastedTwoText").addClass("hidden");
}
});

// pasting
pb.submit(function() {
// We keep the form around for compatibility with no-JS browsers, but
Expand Down
4 changes: 2 additions & 2 deletions fluffy/static/js/paste-inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ $(document).ready(function() {
/**
* Add or remove "selected" classes from the line number and its line.
*/
var els = $('#LL' + num + ',#line-' + num);
var els = $('.LL' + num + ',.line-' + num);
if (isSelected) {
els.addClass('selected');
} else {
Expand Down Expand Up @@ -174,7 +174,7 @@ $(document).ready(function() {
updateLineClasses(line, true);

if (!hasMovedDown) {
var e = $('#LL' + line);
var e = $('.LL' + line);
hasMovedDown = true;
if ($('body').scrollTop() == 0) {
// Chrome needs body, Firefox needs html :\
Expand Down
2 changes: 1 addition & 1 deletion fluffy/static/scss/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ p.error {
}

.hidden {
display: none;
display: none !important;
}

#container {
Expand Down
9 changes: 9 additions & 0 deletions fluffy/static/scss/home.scss
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,15 @@
height: 600px;
}

#pastedTwoText {
display: flex;
column-gap: 10px;

textarea {
flex: 1;
}
}

select {
box-sizing: border-box;
padding: 3px;
Expand Down
11 changes: 11 additions & 0 deletions fluffy/static/scss/text.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,16 @@
font-size: 14px;
position: relative;
background-color: white;
display: flex;

.text-container {
border-left: solid 1px #333;
flex: 1;
min-width: 0; /* https://css-tricks.com/flexbox-truncated-text/ */
}

.text-container:first-child {
border-left: none;
}
}
}
13 changes: 10 additions & 3 deletions fluffy/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,21 @@
<form class="pastebinForm" method="POST" action="/paste">
<p class="switch-modes"><a>upload files instead</a></p>

<p>
<textarea id="text" name="text" placeholder="paste text here" required autofocus>{{text}}</textarea>
<p id="pastedText">
<textarea id="text" name="text" placeholder="paste text here" autofocus>{{text}}</textarea>
</p>

<div class="hidden" id="pastedTwoText">
<textarea name="diff1" placeholder="paste text here for document 1" autofocus></textarea>
<textarea name="diff2" placeholder="paste text here for document 2"></textarea>
</div>

<p>
<select name="language">
<select name="language" id="language">
<option selected="selected" value="">Guess the language for me</option>
<option value="text">Plain text</option>
<option value="rendered-markdown">Markdown (rendered)</option>
<option value="diff-between-two-texts">Diff between two texts</option>
<option disabled></option>

{% for lang_key, text in languages %}
Expand Down
2 changes: 1 addition & 1 deletion fluffy/templates/layouts/text.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</a>

<form method="POST" action="{{home_url}}">
<input type="hidden" name="text" value="{{text}}" />
<input type="hidden" name="text" value="{{copy_and_edit_text}}" />
<input type="submit" class="button" value="Copy &amp; Edit" />
</form>

Expand Down
6 changes: 4 additions & 2 deletions fluffy/templates/markdown.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
{% endblock info %}

{% block text %}
<div class="text">
{{text|markdown|safe}}
<div class="text-container">
<div class="text">
{{text|markdown|safe}}
</div>
</div>
{% endblock %}

Expand Down
25 changes: 16 additions & 9 deletions fluffy/templates/paste.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
{% endblock %}

{% block info %}
{{num_lines(text)}} {{'line'|pluralize(num_lines(text))}} of {{highlighter.name}}
{% if texts|length == 1 %}
{{num_lines(texts[0])}} {{'line'|pluralize(num_lines(texts[0]))}} of
{% endif %}
{{highlighter.name}}
{% endblock %}

{% block highlight_start %}
Expand All @@ -31,14 +34,18 @@
{% endblock %}

{% block text %}
<div class="line-numbers">
{% for i in range(1, num_lines(text) + 1) %}
<a id="LL{{i}}">{{i}}</a>
{% endfor %}
</div>
<div class="text" contenteditable="true" spellcheck="false">
{{highlighter.highlight(text)|safe}}
</div>
{% for text in texts %}
<div class="text-container">
<div class="line-numbers">
{% for i in range(1, num_lines(text) + 1) %}
<a class="LL{{i}}">{{i}}</a>
{% endfor %}
</div>
<div class="text" contenteditable="true" spellcheck="false">
{{highlighter.highlight(text)|safe}}
</div>
</div>
{% endfor %}
{% endblock %}

{% block extra_toolbar %}
Expand Down
31 changes: 24 additions & 7 deletions fluffy/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import concurrent.futures
import contextlib
import difflib
import json
import time
import typing
Expand Down Expand Up @@ -82,12 +83,14 @@ def upload():
except UnicodeDecodeError:
pass
else:
highlighter = get_highlighter(text, None, uf.human_name)
pb = ctx.enter_context(
HtmlToStore.from_html(
render_template(
'paste.html',
text=text,
highlighter=get_highlighter(text, None, uf.human_name),
texts=highlighter.prepare_text(text),
copy_and_edit_text=text,
highlighter=highlighter,
raw_url=app.config['FILE_URL'].format(name=uf.name),
styles=STYLES_BY_CATEGORY,
),
Expand Down Expand Up @@ -170,10 +173,22 @@ def upload():
@app.route('/paste', methods={'POST'})
def paste():
"""Paste and redirect."""
text = request.form['text']
lang = request.form['language']

# Browsers always send \r\n for the pasted text, which leads to bad
# newlines when curling the raw text (#28).
transformed_text = text.replace('\r\n', '\n')
transformed_text = request.form.get('text', '').replace('\r\n', '\n')
diff1 = request.form.get('diff1', '').replace('\r\n', '\n')
diff2 = request.form.get('diff2', '').replace('\r\n', '\n')

if lang == 'diff-between-two-texts':
transformed_text = '\n'.join(
# Lines may or may not end in newlines already (difflib inserts
# them on lines it adds, but not on input lines, and the last input
# line won't have a newline).
line.rstrip('\n') for line in difflib.unified_diff(diff1.splitlines(), diff2.splitlines())
)
lang = 'diff'

with contextlib.ExitStack() as ctx:
objects = []
Expand All @@ -192,13 +207,14 @@ def paste():
# HTML view (Markdown or paste)
lang = request.form['language']
if lang != 'rendered-markdown':
highlighter = get_highlighter(text, lang, None)
highlighter = get_highlighter(transformed_text, lang, None)
lang_title = highlighter.name
paste_obj = ctx.enter_context(
HtmlToStore.from_html(
render_template(
'paste.html',
text=text,
texts=highlighter.prepare_text(transformed_text),
copy_and_edit_text=transformed_text,
highlighter=highlighter,
raw_url=app.config['FILE_URL'].format(name=uf.name),
styles=STYLES_BY_CATEGORY,
Expand All @@ -212,7 +228,8 @@ def paste():
HtmlToStore.from_html(
render_template(
'markdown.html',
text=text,
text=transformed_text,
copy_and_edit_text=transformed_text,
raw_url=app.config['FILE_URL'].format(name=uf.name),
),
),
Expand Down
41 changes: 41 additions & 0 deletions tests/integration/paste_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,47 @@ def test_simple_paste(content, running_server):
)


def test_simple_paste_diff_between_two_texts(running_server):
req = requests.post(
running_server['home'] + '/paste',
data={
'diff1': (
'line A\n'
'line B\n'
'line C\n'
),
'diff2': (
'line B\n'
'line B2\n'
'line C\n'
'line D\n'
),
'language': 'diff-between-two-texts',
},
)

assert req.status_code == 200
pqq = pq(req.content.decode('utf8'))

assert (
pqq.find('input[name=text]').attr('value') ==
'--- \n'
'+++ \n'
'@@ -1,3 +1,4 @@\n'
'-line A\n'
' line B\n'
'+line B2\n'
' line C\n'
'+line D'
)

assert ' -line A' in pq(pqq.find('.text')[0]).text()
assert ' -line A' not in pq(pqq.find('.text')[1]).text()

assert ' +line B2' not in pq(pqq.find('.text')[0]).text()
assert ' +line B2' in pq(pqq.find('.text')[1]).text()


def test_simple_paste_json(running_server):
req = requests.post(
running_server['home'] + '/paste?json',
Expand Down

0 comments on commit d7b27c0

Please sign in to comment.