Skip to content

Commit f2f2cc7

Browse files
committed
Merge remote-tracking branch 'origin/main' into codemirror
2 parents c08ac44 + eb169fa commit f2f2cc7

File tree

6 files changed

+477
-449
lines changed

6 files changed

+477
-449
lines changed

.github/workflows/close-inactive-issues.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jobs:
2222
2323
Thanks for your help!
2424
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
25+
exempt-all-milestones: true
2526
days-before-pr-stale: -1
2627
days-before-pr-close: -1
2728
exempt-issue-labels: "new plugin idea, todo, enhancement, bug, Low priority, documentation"

bun.lock

Lines changed: 259 additions & 439 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/editorFile.js

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -828,26 +828,44 @@ export default class EditorFile {
828828
}
829829

830830
const protocol = Url.getProtocol(this.#uri);
831-
let fs;
831+
const text = this.session.doc.toString();
832+
832833
if (/s?ftp:/.test(protocol)) {
833834
// if file is a ftp or sftp file, get file content from cached file.
834835
// remove ':' from protocol because cache file of remote files are
835836
// stored as ftp102525465N i.e. protocol + id
837+
// Cache files are local file:// URIs, so native file reading works
836838
const cacheFilename = protocol.slice(0, -1) + this.id;
837-
const cacheFile = Url.join(CACHE_STORAGE, cacheFilename);
838-
fs = fsOperation(cacheFile);
839-
} else {
840-
fs = fsOperation(this.uri);
839+
const cacheFileUri = Url.join(CACHE_STORAGE, cacheFilename);
840+
841+
try {
842+
return await system.compareFileText(cacheFileUri, this.encoding, text);
843+
} catch (error) {
844+
console.error("Native compareFileText failed:", error);
845+
return false;
846+
}
847+
}
848+
849+
if (/^(file|content):/.test(protocol)) {
850+
// file:// and content:// URIs can be handled by native Android code
851+
// Native reads file AND compares in background thread
852+
try {
853+
return await system.compareFileText(this.uri, this.encoding, text);
854+
} catch (error) {
855+
console.error("Native compareFileText failed:", error);
856+
return false;
857+
}
841858
}
842859

843860
try {
861+
const fs = fsOperation(this.uri);
844862
const oldText = await fs.readFile(this.encoding);
845863
const text = this.session.doc.toString();
846864

847-
if (oldText.length !== text.length) return true;
848-
return oldText !== text;
865+
// Offload string comparison to background thread
866+
return await system.compareTexts(oldText, text);
849867
} catch (error) {
850-
window.log("error", error);
868+
console.error(error);
851869
return false;
852870
}
853871
}

src/plugins/system/android/com/foxdebug/system/System.java

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@
7878
import java.io.InputStream;
7979
import java.io.OutputStream;
8080
import java.io.IOException;
81+
import java.io.BufferedReader;
82+
import java.io.InputStreamReader;
8183

8284
import android.os.Build;
8385
import android.os.Environment;
@@ -169,6 +171,8 @@ public boolean execute(
169171
case "decode":
170172
case "encode":
171173
case "copyToUri":
174+
case "compare-file-text":
175+
case "compare-texts":
172176
break;
173177
case "get-configuration":
174178
getConfiguration(callbackContext);
@@ -505,6 +509,12 @@ public void run() {
505509
case "encode":
506510
encode(arg1, arg2, callbackContext);
507511
break;
512+
case "compare-file-text":
513+
compareFileText(arg1, arg2, arg3, callbackContext);
514+
break;
515+
case "compare-texts":
516+
compareTexts(arg1, arg2, callbackContext);
517+
break;
508518
default:
509519
break;
510520
}
@@ -616,6 +626,146 @@ private void encode(
616626
}
617627
}
618628

629+
/**
630+
* Compares file content with provided text.
631+
* This method runs in a background thread to avoid blocking the UI.
632+
*
633+
* @param fileUri The URI of the file to read (file:// or content://)
634+
* @param encoding The character encoding to use when reading the file
635+
* @param currentText The text to compare against the file content
636+
* @param callback Returns 1 if texts are different, 0 if same
637+
*/
638+
private void compareFileText(
639+
String fileUri,
640+
String encoding,
641+
String currentText,
642+
CallbackContext callback
643+
) {
644+
try {
645+
if (fileUri == null || fileUri.isEmpty()) {
646+
callback.error("File URI is required");
647+
return;
648+
}
649+
650+
if (encoding == null || encoding.isEmpty()) {
651+
encoding = "UTF-8";
652+
}
653+
654+
if (!Charset.isSupported(encoding)) {
655+
callback.error("Charset not supported: " + encoding);
656+
return;
657+
}
658+
659+
Uri uri = Uri.parse(fileUri);
660+
Charset charset = Charset.forName(encoding);
661+
String fileContent;
662+
663+
// Handle file:// URIs
664+
if ("file".equalsIgnoreCase(uri.getScheme())) {
665+
File file = new File(uri.getPath());
666+
667+
// Validate file
668+
if (!file.exists()) {
669+
callback.error("File does not exist");
670+
return;
671+
}
672+
if (!file.isFile()) {
673+
callback.error("Path is not a file");
674+
return;
675+
}
676+
if (!file.canRead()) {
677+
callback.error("File is not readable");
678+
return;
679+
}
680+
681+
Path path = file.toPath();
682+
fileContent = new String(Files.readAllBytes(path), charset);
683+
684+
} else {
685+
// Handle content:// URIs
686+
InputStream inputStream = null;
687+
try {
688+
inputStream = context.getContentResolver().openInputStream(uri);
689+
690+
if (inputStream == null) {
691+
callback.error("Cannot open file");
692+
return;
693+
}
694+
695+
StringBuilder sb = new StringBuilder();
696+
try (BufferedReader reader = new BufferedReader(
697+
new InputStreamReader(inputStream, charset))) {
698+
char[] buffer = new char[8192];
699+
int charsRead;
700+
while ((charsRead = reader.read(buffer)) != -1) {
701+
sb.append(buffer, 0, charsRead);
702+
}
703+
}
704+
fileContent = sb.toString();
705+
706+
} finally {
707+
if (inputStream != null) {
708+
try {
709+
inputStream.close();
710+
} catch (IOException ignored) {}
711+
}
712+
}
713+
}
714+
715+
// check length first
716+
if (fileContent.length() != currentText.length()) {
717+
callback.success(1); // Changed
718+
return;
719+
}
720+
721+
// Full comparison
722+
if (fileContent.equals(currentText)) {
723+
callback.success(0); // Not changed
724+
} else {
725+
callback.success(1); // Changed
726+
}
727+
728+
} catch (Exception e) {
729+
callback.error(e.toString());
730+
}
731+
}
732+
733+
/**
734+
* Compares two text strings.
735+
* This method runs in a background thread to avoid blocking the UI
736+
* for large string comparisons.
737+
*
738+
* @param text1 First text to compare
739+
* @param text2 Second text to compare
740+
* @param callback Returns 1 if texts are different, 0 if same
741+
*/
742+
private void compareTexts(
743+
String text1,
744+
String text2,
745+
CallbackContext callback
746+
) {
747+
try {
748+
if (text1 == null) text1 = "";
749+
if (text2 == null) text2 = "";
750+
751+
// check length first
752+
if (text1.length() != text2.length()) {
753+
callback.success(1); // Changed
754+
return;
755+
}
756+
757+
// Full comparison
758+
if (text1.equals(text2)) {
759+
callback.success(0); // Not changed
760+
} else {
761+
callback.success(1); // Changed
762+
}
763+
764+
} catch (Exception e) {
765+
callback.error(e.toString());
766+
}
767+
}
768+
619769
private void getAvailableEncodings(CallbackContext callback) {
620770
try {
621771
Map < String, Charset > charsets = Charset.availableCharsets();

src/plugins/system/www/plugin.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,5 +155,44 @@ module.exports = {
155155
},
156156
getGlobalSetting: function (key, onSuccess, onFail) {
157157
cordova.exec(onSuccess, onFail, 'System', 'get-global-setting', [key]);
158+
},
159+
/**
160+
* Compare file content with provided text in a background thread.
161+
* @param {string} fileUri - The URI of the file to read
162+
* @param {string} encoding - The character encoding to use
163+
* @param {string} currentText - The text to compare against
164+
* @returns {Promise<boolean>} - Resolves to true if content differs, false if same
165+
*/
166+
compareFileText: function (fileUri, encoding, currentText) {
167+
return new Promise((resolve, reject) => {
168+
cordova.exec(
169+
function(result) {
170+
resolve(result === 1);
171+
},
172+
reject,
173+
'System',
174+
'compare-file-text',
175+
[fileUri, encoding, currentText]
176+
);
177+
});
178+
},
179+
/**
180+
* Compare two text strings in a background thread.
181+
* @param {string} text1 - First text to compare
182+
* @param {string} text2 - Second text to compare
183+
* @returns {Promise<boolean>} - Resolves to true if texts differ, false if same
184+
*/
185+
compareTexts: function (text1, text2) {
186+
return new Promise((resolve, reject) => {
187+
cordova.exec(
188+
function(result) {
189+
resolve(result === 1);
190+
},
191+
reject,
192+
'System',
193+
'compare-texts',
194+
[text1, text2]
195+
);
196+
});
158197
}
159198
};

src/plugins/terminal/www/Executor.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Executor {
4747
const match = message.match(/^([^:]+):(.*)$/);
4848
if (match) {
4949
const prefix = match[1]; // e.g. "stdout"
50-
const content = match[2].trim(); // output
50+
const content = match[2]; // output
5151
onData(prefix, content);
5252
} else {
5353
onData("unknown", message);
@@ -198,4 +198,4 @@ class Executor {
198198
const executorInstance = new Executor();
199199
executorInstance.BackgroundExecutor = new Executor(true);
200200

201-
module.exports = executorInstance;
201+
module.exports = executorInstance;

0 commit comments

Comments
 (0)