Rewrote file logic
mellobacon committed
Nov 27, 2022 at 18:04 UTC
bfc91e0aad1a051ede0112f0cb0d99fd232b2934
13 files changed
+210
-204
src/components/Editor/CodeMirrorEditor.svelte
+31
-4
@@ -5,14 +5,32 @@
5
import { indentWithTab } from "@codemirror/commands";
6
import { EditorState, Compartment } from "@codemirror/state";
7
import { default_theme } from "./scripts/DefaultTheme";
8
- import { line_info } from "./scripts/Editor";
8
+ import { file_language, file_linefeed, line_info } from "./scripts/Editor";
9
10
export let hidden = false;
11
let editorElement;
12
let editorView: EditorView;
13
export let content = "";
14
export let lang = new Compartment();
15
+ let file_info;
16
+ let _ = null;
17
18
+ export function getFileContent() {
19
+ return content;
20
+ }
21
+ export function getFileInfo() {
22
+ return file_info;
23
+ }
24
+ export function setFileInfo(file) {
25
+ let name = file.filename;
26
+ let path = file.path;
27
+ let linefeed = file.linefeed;
28
+ let language = file.language.name;
29
+ setLanguageMode(file.language.mode);
30
+ file_linefeed.set(file.linefeed);
31
+ file_info = {name, path, linefeed, language};
32
+ }
33
+
34
export function setLanguageMode(mode) {
35
editorView.dispatch({
36
effects: lang.reconfigure(mode)
@@ -21,13 +39,15 @@
39
export async function focus() {
40
await tick();
41
await tick();
42
+ file_linefeed.set(file_info.linefeed);
43
+ file_language.set(file_info.language);
44
editorView.focus();
45
getLineInfo();
46
}
47
function getLineInfo() {
48
let linenumber = editorView.state.doc.lineAt(editorView.state.selection.main.head).number;
49
let colnumber = editorView.state.selection.ranges[0].head - editorView.state.doc.lineAt(editorView.state.selection.main.head).from
30
- line_info.set({line: linenumber.toString(), col: (colnumber + 1).toString()})
50
+ line_info.set({line: linenumber.toString(), col: (colnumber + 1).toString()});
51
}
52
async function updateDom() {
53
await tick();
@@ -35,9 +55,16 @@
55
for (let text of editorView.state.doc) {
56
filecontent += `${text} `;
57
}
38
- dispatch("input", filecontent);
58
+ content = filecontent;
59
+ // save content to file
60
+ clearTimeout(_);
61
+ _ = setTimeout(() => {
62
+ if (file_info.path !== "") {
63
+ //writeFile(file_info.path, filecontent);
64
+ console.log(`${file_info.name} saved`);
65
+ }
66
+ }, 1000)
67
}
40
- let dispatch = createEventDispatcher();
68
69
onMount(() => {
70
editorView = new EditorView({
src/components/Editor/scripts/Editor.ts
+34
-17
@@ -1,26 +1,43 @@
1
import langlist from "../../../scripts/languages/languages.json";
2
-import { languages } from "@codemirror/language-data";
2
import { writable } from 'svelte/store';
3
+import { languages as cmLangs } from "@codemirror/language-data";
4
5
+let lang;
6
+export let langsupported = writable(true);
7
+export let limitedsupport = writable(false);
8
export let line_info = writable({line: "-", col: "-"});
6
-
9
+export let file_language = writable("Plain Text");
10
+export let file_linefeed = writable("-");
11
let keys = Object.keys(langlist);
8
-export function getLang(ext: string) {
9
- for (let key of keys) {
10
- let extensions = langlist[key].extensions;
11
- if (extensions && extensions.includes(`.${ext}`)) {
12
- return `${key}`;
13
- }
12
+export async function getLang(ext: string) {
13
+ let language = "Unknown";
14
+ let mode = null;
15
+ if (ext === "") {
16
+ ext = "txt";
17
}
15
-}
16
-
17
-export async function getLangMode(language: string) {
18
- // TODO: So apparently not every language is supported in CodeMirror. I need to find a way to support that
19
- let mode = languages.find(l => l.name === language);
20
- if (mode) {
21
- return await mode.load();
18
+ let cmlang = cmLangs.find(l => l.extensions.includes(ext));
19
+
20
+ // Check if theres official support for the language first before checking the json file
21
+ if (cmlang) {
22
+ language = cmlang.name;
23
+ mode = await cmlang.load();
24
+ langsupported.set(true);
25
}
26
else {
24
- return [];
27
+ for (let key of keys) {
28
+ let extensions = langlist[key].extensions;
29
+ if (extensions && extensions.includes(`.${ext}`)) {
30
+ language = key;
31
+ limitedsupport.set(true);
32
+ break;
33
+ }
34
+ else {
35
+ langsupported.set(false);
36
+ }
37
+ }
38
}
26
-}
\ No newline at end of file
39
+ lang = {name: language, mode};
40
+ file_language.set(language);
41
+ return lang;
42
+}
43
+
src/components/FileTree/scripts/TreeData.ts
-15
@@ -12,21 +12,6 @@ export async function data() {
12
return await loadTree();
13
}
14
15
-export function createFile(filename = "", path = "") {
16
- return new EditorFile(filename, path, getLF(""), "");
17
-}
18
-export async function loadFile(path: string) {
19
- if (path === null) return;
20
- let filename = path.split(sep).pop();
21
- let content = await readTextFile(path);
22
- let linefeed = getLF(content);
23
- return new EditorFile(filename, path, linefeed, content);
24
-}
25
-
26
-function getLF(file) {
27
- return file.includes('\r\n') ? 'CRLF' : 'LF'
28
-}
29
-
15
async function loadTree() {
16
let children = await fs.readDir(dir, { recursive: true });
17
id = 0;
src/components/Footer/Footer.svelte
+4
-4
@@ -1,16 +1,16 @@
1
<script lang="ts">
2
import { Terminal } from "carbon-icons-svelte"
3
- import { isfile, file_language, linefeed } from "../Tabs/scripts/Tab";
4
- import { line_info } from "../Editor/scripts/Editor";
3
+ import { isfile } from "../Tabs/scripts/Tab";
4
import { invoke } from "@tauri-apps/api/tauri";
5
import { homeDir } from '@tauri-apps/api/path';
6
import { filetree } from "../FileTree/scripts/TreeStore";
7
import LanguageList from "./LanguageList.svelte";
8
import { languages } from "@codemirror/language-data";
9
+ import { file_language, file_linefeed, line_info } from "../Editor/scripts/Editor";
10
11
let langs = [];
12
for (let l of languages) {
13
- let langdata = {lang: l.name, tags: l.extensions}
13
+ let langdata = {lang: l.name, tags: l.extensions, mode: l.load()};
14
langs = [...langs, langdata];
15
}
16
let showlangs = false;
@@ -64,7 +64,7 @@
64
</div>
65
{#if $isfile}
66
<div id="codeinfo">
67
- <span title="End of Line Sequence">{$linefeed}</span>
67
+ <span title="End of Line Sequence">{$file_linefeed}</span>
68
<span>{$line_info.line} : {$line_info.col}</span>
69
<!-- svelte-ignore a11y-click-events-have-key-events -->
70
<span on:click={
src/components/Footer/LanguageList.svelte
+6
-3
@@ -6,7 +6,7 @@
6
export let langs;
7
8
let selectedlang = "None";
9
- let selected = false;
9
+ let langmode = null;
10
</script>
11
12
<Modal
@@ -21,8 +21,10 @@
21
<div id="langlist">
22
<div><span class="note">Missing a language? Search for new ones in Extensions ></span></div>
23
{#each langs as language}
24
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
25
<div class="language" on:click={() => {
26
selectedlang = language.lang;
27
+ langmode = language.mode;
28
}}>
29
<span class="name">{language.lang}</span>
30
<span class="tags">
@@ -34,8 +36,9 @@
36
{/each}
37
</div>
38
<div id="selected">Selected: {selectedlang}</div>
37
- <div id="button" on:click={() => {
38
- setFileLanguage(selectedlang);
39
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
40
+ <div id="button" on:click={async() => {
41
+ setFileLanguage(selectedlang, langmode);
42
showlangs = false;
43
}}>
44
Select
src/components/Header/header_menus/FileMenu.ts
+9
-3
@@ -1,7 +1,7 @@
1
import { filetree, workspacename } from "../../FileTree/scripts/TreeStore";
2
import { data, workspace } from "../../FileTree/scripts/TreeData";
3
-import { addFileTab } from "../../Tabs/scripts/Tab";
4
-import { getFile, saveFile } from "../../../scripts/EditorFile";
3
+import { addFileTab, addSettingsTab } from "../../Tabs/scripts/Tab";
4
+import { getFile, saveFile, saveFileAs } from "../../../scripts/EditorFile";
5
6
export async function addNewFile() {
7
await addFileTab();
@@ -18,5 +18,11 @@ export async function openFolder() {
18
workspacename.set(workspace());
19
}
20
export async function saveOpenFile() {
21
- await saveFile();
21
+ await saveFile()
22
+}
23
+export async function saveOpenFileAs() {
24
+ await saveFileAs();
25
+}
26
+export function openSettings() {
27
+ addSettingsTab();
28
}
\ No newline at end of file
src/components/Header/menu.ts
+2
-3
@@ -1,5 +1,5 @@
1
import { appWindow } from "@tauri-apps/api/window";
2
-import { addNewFile, openFile, openFolder, saveOpenFile } from "./header_menus/FileMenu";
2
+import { addNewFile, openFile, openFolder, openSettings, saveOpenFile } from "./header_menus/FileMenu";
3
export const filemenu = [
4
{ option: "New File...", shortcut: "Ctrl + N", onclick: addNewFile },
5
{ option: "Open File...", shortcut: "Ctrl + O", onclick: openFile },
@@ -7,8 +7,7 @@ export const filemenu = [
7
{ option: "Open Recent", onclick: () => { console.log("click"); } },
8
{ option: "Save File", shortcut: "Ctrl + S", onclick: saveOpenFile, divider: true },
9
{ option: "Save File As...", shortcut: "Ctrl + Shift + S", onclick: saveOpenFile },
10
- { option: "Save All", shortcut: "Ctrl + Shift + K", onclick: () => { console.log("click"); } },
11
- { option: "Settings", onclick: () => { console.log("click"); }, divider: true },
10
+ { option: "Settings", onclick: openSettings, divider: true },
11
{ option: "Exit", onclick: async () => { await appWindow.close(); } }
12
];
13
export const editmenu = [
src/components/Modal/RenameModel.svelte
+3
-3
@@ -5,7 +5,7 @@
5
import { updateTree } from "../FileTree/scripts/TreeData";
6
import langlist from "../../scripts/languages/languages.json";
7
import { sep } from "@tauri-apps/api/path";
8
- import { renameFile, setFileLanguage } from "../../scripts/EditorFile";
8
+ import { renameFile } from "../../scripts/EditorFile";
9
export let open = false;
10
export let filename = "";
11
export let path = "/";
@@ -69,6 +69,7 @@
69
</div>
70
<div class="buttongroup">
71
<div class="button disabled">Back</div>
72
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
73
<div
74
class="button"
75
on:click={async () => {
@@ -92,8 +93,7 @@
93
path = newpath.join(sep);
94
95
await fs.renameFile(oldpath, path);
95
- renameFile(filenameinput, path);
96
- setFileLanguage(extinput);
96
+ await renameFile(filenameinput, path);
97
await updateTree();
98
open = false;
99
}}
src/components/Tabs/Tab.svelte
+4
-4
@@ -21,10 +21,10 @@
21
{/if}
22
</div>
23
<!-- svelte-ignore a11y-click-events-have-key-events -->
24
- <div class="close-tab" on:click={() => {
25
- closeTab(id);
26
- }}>
27
- <span></span>
24
+ <div class="close-tab">
25
+ <span on:click={() => {
26
+ closeTab(id);
27
+ }}></span>
28
</div>
29
</div>
30
src/components/Tabs/scripts/Tab.ts
+40
-111
@@ -1,14 +1,11 @@
1
-import { writable } from 'svelte/store';
2
-import { EditorFile, getFileData } from '../../../scripts/EditorFile'
1
import CodeMirrorEditor from '../../Editor/CodeMirrorEditor.svelte';
4
-import { writeFile } from '@tauri-apps/api/fs';
5
-import { getLang, getLangMode } from '../../Editor/scripts/Editor';
2
+import Settings from '../../Settings/Settings.svelte';
3
+import { writable } from 'svelte/store';
4
+import { getFileData } from '../../../scripts/EditorFile';
5
6
let id = 0;
7
let activeid;
8
10
-export let file_language = writable("");
11
-export let linefeed = writable("");
9
export let tablist: Tab[] = [];
10
export let tabs = writable([]);
11
export let hidden = writable(true);
@@ -18,111 +15,40 @@ class Tab {
15
id: number;
16
label: string;
17
active: boolean;
21
- constructor(id: number, label: string = "") {
18
+ path: string;
19
+ content;
20
+ isfile: boolean;
21
+ saved: boolean;
22
+ constructor(id, label = "", content = null, path = "") {
23
this.id = id;
24
this.label = label === "" ? `Untitled-${id}` : label;
24
- }
25
-}
26
-
27
-export class FileTab extends Tab {
28
- path: string;
29
- language: string = "Plain Text";
30
- linefeed: string;
31
- saved: boolean = true;
32
- editor: CodeMirrorEditor | null;
33
- editorcontent: string;
34
- constructor(id: number, file: EditorFile, editor, saved: boolean = true) {
35
- super(id, file.filename);
36
- this.path = file.path;
37
- this.saved = saved;
38
- this.editor = editor;
39
- this.editorcontent = file.content;
40
-
41
- let filepath = file.path.split(".");
42
- let extension = "txt";
43
- if (filepath.length !== 1) {
44
- extension = filepath.at(-1);
45
- }
46
-
47
- this.language = getLang(extension)
48
- this.linefeed = file.linefeed;
49
-
50
- let _ = undefined;
51
- this.editor.$on("input", (e) => {
52
- clearTimeout(_);
53
- _ = setTimeout(() => {
54
- this.editorcontent = e.detail;
55
- if (this.path !== "") {
56
- writeFile(this.path, e.detail);
57
- console.log(`${this.label} saved`);
58
- if (!this.saved) {
59
- this.saved = true;
60
- setActive(this.id);
61
- }
62
- }
63
- else {
64
- this.saved = false;
65
- }
66
- }, 1000)
67
- })
68
- }
69
-
70
- async setLanguage(lang: string) {
71
- let mode = await getLangMode(lang);
72
- this.editor.setLanguageMode(mode);
73
-
74
- file_language.set(lang);
75
- this.language = lang;
76
- }
77
- setFile(file: EditorFile) {
78
- this.label = file.filename === "" ? `Untitled-${this.id}` : file.filename;
79
- this.path = file.path;
80
- let filepath = file.path.split(".");
81
- let extension = "txt";
82
- if (filepath.length !== 1) {
83
- extension = filepath.at(-1);
84
- }
85
- this.language = getLang(extension);
86
-
87
- this.linefeed = file.linefeed;
88
- }
89
- focusTab() {
90
- file_language.set(this.language);
91
- linefeed.set(this.linefeed);
92
- this.editor.focus();
93
- }
94
- updateEditorVisibility(id: number) {
95
- this.editor.$set({ hidden: !(this.id === id) })
96
- }
97
-}
98
-export class SettingsTab extends Tab {
99
- content: string;
100
- constructor(id: number, label: string, content: string) {
101
- super(id, label);
25
+ this.path = path === "" ? this.label : path;
26
this.content = content;
27
}
28
+ updateView(id) {
29
+ this.content.$set({ hidden: !(this.id === id) });
30
+ }
31
}
32
33
export async function addFileTab(path = "") {
107
- for (let t of tablist) {
108
- if ((t as FileTab).path === path) {
109
- setActive(t.id);
110
- }
34
+ if (tabOpen(path)) {
35
+ return;
36
}
37
let file = await getFileData(path);
113
- let editor = new CodeMirrorEditor({ target: document.getElementById("tabview"), props: { content: file.content } });
114
- let tab = new FileTab(id, file, editor, path !== "");
115
- await tab.setLanguage(tab.language);
38
+ let content = new CodeMirrorEditor({ target: document.getElementById("tabview"), props: { content: file.content } });
39
+ let tab = new Tab(id, file.filename, content, file.path);
40
+ tab.isfile = true;
41
+ tab.saved = file.path === "";
42
+ // set language for highlighting here
43
+ content.setFileInfo(file);
44
tablist = [...tablist, tab];
117
-
45
refreshTabs();
46
}
47
48
export function addSettingsTab() {
122
- let content = "";
123
- let tab = new SettingsTab(id, "Settings", content);
49
+ let content = new Settings({target: document.getElementById("tabview")});
50
+ let tab = new Tab(id, "Settings", content);
51
tablist = [...tablist, tab];
125
-
52
refreshTabs();
53
}
54
@@ -131,10 +57,9 @@ export function setActive(id: number) {
57
if (tab.id === id) {
58
activeid = id;
59
tab.active = true;
134
- if (tab instanceof FileTab) {
135
- let t = tab as FileTab;
60
+ if (tab.isfile) {
61
isfile.set(true);
137
- t.focusTab();
62
+ tab.content.focus();
63
}
64
else {
65
isfile.set(false);
@@ -145,7 +70,7 @@ export function setActive(id: number) {
70
}
71
}
72
tabs.set(tablist);
148
- updateEditorVisibility();
73
+ updateView();
74
}
75
76
export function closeTab(tabid: number) {
@@ -161,17 +86,13 @@ export function closeTab(tabid: number) {
86
}
87
}
88
}
164
-
165
- for (let tab of tablist) {
166
- if (tab.id === tabid) {
167
- (tab as FileTab).editor.$destroy();
168
- break;
169
- }
170
- }
89
+
90
+ tablist.find(t => t.id === tabid).content.$destroy();
91
tablist = tablist.filter(t => t.id !== tabid);
92
tabs.set(tablist);
93
+
94
+ updateView();
95
174
- updateEditorVisibility();
96
if (tablist.length === 0) {
97
hidden.set(true);
98
isfile.set(false);
@@ -182,15 +103,23 @@ export function closeTab(tabid: number) {
103
function refreshTabs() {
104
if (tablist.length > 0) {
105
hidden.set(false);
185
- isfile.set(true);
106
}
107
tabs.set(tablist);
108
setActive(id);
109
id++;
110
}
111
192
-function updateEditorVisibility() {
112
+function updateView() {
113
for (let tab of tablist) {
194
- (tab as FileTab).updateEditorVisibility(activeid);
114
+ tab.updateView(activeid);
115
}
116
}
117
+function tabOpen(path) {
118
+ for (const tab of tablist) {
119
+ if (tab.path === path) {
120
+ setActive(tab.id);
121
+ return true;
122
+ }
123
+ }
124
+ return false;
125
+}
\ No newline at end of file
src/components/View/Editor.svelte
renamed
src/components/View/MainView.svelte
+1
-1
@@ -1,6 +1,6 @@
1
<script lang="ts">
2
import { Splitpanes, Pane } from "svelte-splitpanes";
3
- import Editor from "../Editor/Editor.svelte";
3
+ import Editor from "./Editor.svelte";
4
import Footer, { showpanel, tool } from "../Footer/Footer.svelte";
5
import EditorTabs from "../Tabs/EditorTabs.svelte";
6
src/scripts/EditorFile.ts
+76
-36
@@ -1,71 +1,111 @@
1
import { readTextFile, writeFile } from "@tauri-apps/api/fs";
2
-import { sep } from "@tauri-apps/api/path";
3
-import { dialog } from "@tauri-apps/api";
2
+import { extname, sep } from "@tauri-apps/api/path";
3
+import { dialog, fs } from "@tauri-apps/api";
4
import { setActive, tablist } from "../components/Tabs/scripts/Tab";
5
-import type { FileTab } from "../components/Tabs/scripts/Tab";
5
+import { getLang } from "../components/Editor/scripts/Editor";
6
7
export class EditorFile {
8
filename: string;
9
path: string;
10
content: string;
11
linefeed: string;
12
- constructor(filename: string, path: string, linefeed: string, content: string) {
12
+ language;
13
+ constructor(filename: string, path: string, linefeed: string, language, content: string) {
14
this.filename = filename;
15
this.path = path;
16
this.linefeed = linefeed;
17
this.content = content;
18
+ this.language = language;
19
}
20
}
19
-
21
export async function getFile() {
22
let path = await dialog.open() as string;
22
- if (path === undefined) return;
23
+ if (path === null) return;
24
return path;
25
}
26
export async function getFileData(path = "", label = "") {
27
if (path === null) return;
27
- if (path === "") {
28
- return new EditorFile(label, path, getLF(""), "");
28
+ let language = await getLang("");
29
+ let filename = label;
30
+ let content = "";
31
+ let linefeed = "LF";
32
+ let ext = "txt";
33
+ if (path !== "") {
34
+ try {
35
+ ext = await extname(path);
36
+ } catch (error) {
37
+ console.log("No file extension found. Setting to Plain Text...");
38
+ }
39
+ language = await getLang(ext);
40
+ filename = path.split(sep).pop();
41
+ try {
42
+ content = await readTextFile(path);
43
+ } catch (error) {
44
+ console.log("Cannot read file content. Skipping...");
45
+ }
46
+ linefeed = content.includes("\r\n") ? "CLRF" : "LF";
47
}
30
- let filename = path.split(sep).pop();
31
- let content = await readTextFile(path);
32
- let linefeed = getLF(content);
33
- return new EditorFile(filename, path, linefeed, content);
48
+ return new EditorFile(filename, path, linefeed, language, content);
49
}
35
-
50
export async function saveFile() {
51
for (let tab of tablist) {
38
- if (tab.active) {
39
- let t = tab as FileTab;
40
- if (t.path === "") {
41
- const filePath = await dialog.save({
42
- defaultPath: `${tab.label}.txt`
43
- }) as string;
44
- if (filePath == undefined) return;
45
-
46
- let file = new EditorFile(filePath.split(sep).pop(), filePath, getLF(""), "");
47
- t.setFile(file);
48
- t.saved = true;
52
+ if (tab.active && tab.isfile) {
53
+ let ispath = tab.path.includes(sep);
54
+ if (!ispath) {
55
+ const path = await dialog.save({defaultPath: `${tab.label}.txt`});
56
+ if (path === null) return;
57
+ await fs.writeFile(path, tab.content.getFileContent());
58
+ const label = path.split(sep).pop();
59
+ let file = await getFileData(path, label);
60
+ tab.content.setFileInfo(file);
61
+ tab.label = label;
62
+ tab.path = path;
63
+ tab.saved = !tab.saved;
64
setActive(tab.id);
65
}
66
else {
52
- writeFile(t.path, t.editorcontent);
67
+ await fs.writeFile(tab.path, tab.content.getFileContent());
68
}
69
+ break;
70
+ }
71
+ }
72
+}
73
+export async function saveFileAs() {
74
+ for (let tab of tablist) {
75
+ if (tab.active && tab.isfile) {
76
+ const path = await dialog.save({defaultPath: `${tab.label}.txt`});
77
+ if (path === null) return;
78
+ await fs.writeFile(path, tab.content.getFileContent());
79
+ const label = path.split(sep).pop();
80
+ let file = await getFileData(path, label);
81
+ tab.content.setFileInfo(file);
82
+ tab.label = label;
83
+ tab.path = path;
84
+ tab.saved = !tab.saved;
85
+ setActive(tab.id);
86
+ break;
87
}
88
}
89
}
90
58
-export function renameFile(label, path) {
59
- let tab = tablist.find(t => t.active);
60
- tab.label = label;
61
- (tab as FileTab).path = path;
62
- setActive(tab.id);
91
+export async function renameFile(label, path) {
92
+ for (const tab of tablist) {
93
+ if (tab.isfile) {
94
+ tab.label = label;
95
+ tab.path = path;
96
+ const file = await getFileData(path, label);
97
+ tab.content.setFileInfo(file);
98
+ setActive(tab.id);
99
+ break;
100
+ }
101
+ }
102
}
103
65
-export function setFileLanguage(lang) {
66
- let tab = tablist.find(t => t.active) as FileTab;
67
- tab.setLanguage(lang);
104
+export function setFileLanguage(lang, mode = null) {
105
+ for (const tab of tablist) {
106
+ if (tab.active && tab.isfile) {
107
+ tab.content.setLanguage(lang, mode);
108
+ break;
109
+ }
110
+ }
111
}
69
-function getLF(content: string) {
70
- return content.includes('\r\n') ? 'CRLF' : 'LF'
71
-}
\ No newline at end of file