@setoelkahfi / svara / commits / bfc91e0

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