Skip to content

Commit ab4c693

Browse files
committed
Use chardet to preserve file encoding
1 parent a595f80 commit ab4c693

File tree

5 files changed

+56
-41
lines changed

5 files changed

+56
-41
lines changed

preditor/gui/workbox_mixin.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import absolute_import, print_function
22

3+
import io
34
import os
45
import tempfile
56
import textwrap
67

8+
import chardet
79
from Qt.QtCore import Qt
810
from Qt.QtWidgets import QStackedWidget
911

@@ -317,14 +319,37 @@ def __remove_tempfile__(self):
317319
os.remove(tempfile)
318320

319321
@classmethod
320-
def __open_file__(cls, filename):
321-
with open(filename) as fle:
322-
return fle.read()
323-
return ""
322+
def __open_file__(cls, filename, strict=True):
323+
"""Open a file and try to detect the text encoding it was saved as.
324+
325+
Returns:
326+
encoding(str): The detected encoding, Defaults to "utf-8" if unable
327+
to detect encoding.
328+
text(str): The contents of the file decoded to a str.
329+
"""
330+
with open(filename, "rb") as f:
331+
text_bytes = f.read()
332+
333+
# Open file, detect source encoding and convert to utf-8
334+
encoding = chardet.detect(text_bytes)['encoding'] or 'utf-8'
335+
try:
336+
text = text_bytes.decode(encoding)
337+
except UnicodeDecodeError as e:
338+
if strict:
339+
raise UnicodeDecodeError( # noqa: B904
340+
e.encoding,
341+
e.object,
342+
e.start,
343+
e.end,
344+
f"{e.reason}, Filename: {filename}",
345+
)
346+
encoding = 'utf-8'
347+
text = text_bytes.decode(encoding, errors="ignore")
348+
return encoding, text
324349

325350
@classmethod
326-
def __write_file__(cls, filename, txt):
327-
with open(filename, 'w') as fle:
351+
def __write_file__(cls, filename, txt, encoding=None):
352+
with io.open(filename, 'w', newline='\n', encoding=encoding) as fle:
328353
fle.write(txt)
329354

330355
def __show__(self):
@@ -335,7 +360,7 @@ def __show__(self):
335360
if self._filename_pref:
336361
self.__load__(self._filename_pref)
337362
elif self._tempfile:
338-
txt = self.__open_file__(self.__tempfile__())
363+
_, txt = self.__open_file__(self.__tempfile__(), strict=False)
339364
self.__set_text__(txt)
340365

341366
def process_shortcut(self, event, run=True):

preditor/gui/workbox_text_edit.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(
2727
):
2828
super(WorkboxTextEdit, self).__init__(parent=parent, core_name=core_name)
2929
self._filename = None
30+
self._encoding = None
3031
self.__set_console__(console)
3132
highlight = CodeHighlighter(self)
3233
highlight.setLanguage('Python')
@@ -79,7 +80,8 @@ def __set_indentations_use_tabs__(self, state):
7980

8081
def __load__(self, filename):
8182
self._filename = filename
82-
txt = self.__open_file__(self._filename)
83+
enc, txt = self.__open_file__(self._filename)
84+
self._encoding = enc
8385
self.__set_text__(txt)
8486

8587
def __margins_font__(self):

preditor/gui/workboxwidget.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import absolute_import, print_function
22

3-
import io
43
import re
54
import time
65

@@ -197,10 +196,10 @@ def __set_text__(self, txt):
197196
self.setText(txt)
198197

199198
@classmethod
200-
def __write_file__(cls, filename, txt):
201-
with io.open(filename, 'w', newline='\n') as fle:
202-
# Save unix newlines for simplicity
203-
fle.write(cls.__unix_end_lines__(txt))
199+
def __write_file__(cls, filename, txt, encoding=None):
200+
# Save unix newlines for simplicity
201+
txt = cls.__unix_end_lines__(txt)
202+
super(WorkboxWidget, cls).__write_file__(filename, txt, encoding=encoding)
204203

205204
def keyPressEvent(self, event):
206205
"""Check for certain keyboard shortcuts, and handle them as needed,

preditor/scintilla/documenteditor.py

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@
2020
from functools import partial
2121

2222
import six
23-
from PyQt5.QtCore import QTextCodec
2423
from Qt import QtCompat
25-
from Qt.QtCore import Property, QFile, QPoint, Qt, Signal
24+
from Qt.QtCore import Property, QPoint, Qt, Signal
2625
from Qt.QtGui import QColor, QFont, QFontMetrics, QIcon
2726
from Qt.QtWidgets import (
2827
QAction,
@@ -37,6 +36,7 @@
3736
from ..delayable_engine import DelayableEngine
3837
from ..enum import Enum, EnumGroup
3938
from ..gui import QtPropertyInit
39+
from ..gui.workbox_mixin import WorkboxMixin
4040
from . import QsciScintilla, lang
4141

4242
logger = logging.getLogger(__name__)
@@ -90,7 +90,7 @@ def __init__(self, parent, filename='', lineno=0, delayable_engine='default'):
9090
self.additionalFilenames = []
9191
self._language = ''
9292
self._lastSearch = ''
93-
self._textCodec = None
93+
self._encoding = 'utf-8'
9494
self._fileMonitoringActive = False
9595
self._marginsFont = self._defaultFont
9696
self._lastSearchDirection = SearchDirection.First
@@ -657,14 +657,8 @@ def lineMarginWidth(self):
657657
def load(self, filename):
658658
filename = str(filename)
659659
if filename and os.path.exists(filename):
660-
f = QFile(filename)
661-
f.open(QFile.ReadOnly)
662-
text = f.readAll()
663-
self._textCodec = QTextCodec.codecForUtfText(
664-
text, QTextCodec.codecForName('UTF-8')
665-
)
666-
self.setText(self._textCodec.toUnicode(text))
667-
f.close()
660+
self._encoding, text = WorkboxMixin.__open_file__(filename)
661+
self.setText(text)
668662
self.updateFilename(filename)
669663
self.enableFileWatching(True)
670664
self.setEolMode(self.detectEndLine(self.text()))
@@ -1091,25 +1085,21 @@ def saveAs(self, filename='', setFilename=True):
10911085
if filename:
10921086
self._saveTimer = time.time()
10931087
# save the file to disk
1094-
f = QFile(filename)
1095-
f.open(QFile.WriteOnly)
1096-
# make sure the file is writeable
1097-
if f.error() != QFile.NoError:
1098-
logger.debug('An error occured while saving')
1088+
try:
1089+
txt = self.text()
1090+
WorkboxMixin.__write_file__(filename, txt, encoding=self._encoding)
1091+
with open(filename, "w", encoding=self._encoding) as f:
1092+
f.write(self.text())
1093+
except PermissionError as error:
1094+
logger.debug('An error occurred while saving')
10991095
QMessageBox.question(
11001096
self.window(),
11011097
'Error saving file...',
1102-
'There was a error saving the file. Error Code: %i' % f.error(),
1098+
'There was a error saving the file. Error: {}'.format(error),
11031099
QMessageBox.StandardButton.Ok,
11041100
)
1105-
f.close()
11061101
return False
1107-
# Attempt to save the file using the same codec that it used to display it
1108-
if self._textCodec:
1109-
f.write(self._textCodec.fromUnicode(self.text()))
1110-
else:
1111-
self.write(f)
1112-
f.close()
1102+
11131103
# notify that the document was saved
11141104
self.documentSaved.emit(self, filename)
11151105

@@ -1710,10 +1700,8 @@ def updateSelectionInfo(self):
17101700
epos=epos,
17111701
lineCount=eline - sline + 1,
17121702
)
1713-
if self._textCodec and self._textCodec.name() != 'System':
1714-
text = 'Encoding: {enc} {text}'.format(
1715-
enc=self._textCodec.name(), text=text
1716-
)
1703+
if self._encoding:
1704+
text = 'Encoding: {enc} {text}'.format(enc=self._encoding, text=text)
17171705
window.uiCursorInfoLBL.setText(text)
17181706

17191707
def setAutoReloadOnChange(self, state):

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ platform = any
2525
packages = find:
2626
install_requires =
2727
Qt.py
28+
chardet
2829
configparser>=4.0.2
2930
future>=0.18.2
3031
python-redmine>=2.1.1

0 commit comments

Comments
 (0)