Compare commits

...

31 Commits

Author SHA1 Message Date
5ba880bc7e WIP 2025-04-19 12:46:22 -04:00
6a4957588d Pass root path to GUI process. 2025-04-13 13:47:12 -04:00
f4242f7749 Factor out TUI code. 2025-04-13 12:17:11 -04:00
fd3c8fb204 Factor out GUI code. 2025-04-13 12:15:31 -04:00
fd9d47f0c0 Clean up. 2025-04-13 12:09:18 -04:00
d53ef9aa1b Launch clide in separate process by default.
Improve CLI to support tui and gui modes.
Also supports attaching the GUI to the current terminal via -g
2025-04-13 11:58:28 -04:00
a29ae43e84 Use CWD if no directory is provided. 2025-04-13 11:01:37 -04:00
41a9a2a3bf Use structopt. 2025-04-13 10:21:02 -04:00
7bf6c3299c Add basic CLI for launching head(less) mode. 2025-04-13 09:35:55 -04:00
90c10d2a16 Debug missing console placeholder text.
It appears on resizing horizontally
2025-04-13 08:31:41 -04:00
2dcf0529d1 Custom highlighting to fix UI bugs.
+ Selecting text caused blurry editor view.
+ Now prefers syntect theme background color over QML background color.
2025-04-12 13:33:39 -04:00
f740ff347b Fix file loading. 2025-03-31 22:56:57 -04:00
9b86553513 Add syntax highlighting with syntect. 2025-03-31 22:32:17 -04:00
365940267f Add some checks before reading file. 2025-03-31 19:26:04 -04:00
b426b88b79 Fix colors. 2025-03-30 21:58:38 -04:00
bdf942371c Add basic FileSystem view. 2025-03-30 21:38:57 -04:00
b62dce631f Add ClideTreeView. 2025-03-30 16:14:58 -04:00
1546eb1028 Add FileSystem Rust module. 2025-03-30 13:50:23 -04:00
4f3aebe64f Add basic stub for filesystem. 2025-03-30 13:14:58 -04:00
413500dad3 Add an about window. 2025-03-30 12:52:44 -04:00
b0064c2f69 Update README. 2025-03-30 12:00:27 -04:00
0f055603a2 Remove unused LineCount. 2025-03-30 11:51:23 -04:00
094ac92fe4 Reorganize qml files. 2025-03-30 11:49:00 -04:00
3b8e407632 Consolidate source files. 2025-03-30 11:47:16 -04:00
d2f5823594 Add RustColors singleton helper. 2025-03-30 11:20:21 -04:00
be9981291c Speed up animation for dock handles. 2025-03-29 20:20:28 -04:00
500a329dea Separate LineCount from main. 2025-03-29 20:14:42 -04:00
70e9f79c8a Separate ProjectView and Editor. 2025-03-29 18:00:16 -04:00
13a405a801 Add LineCount module. 2025-03-29 16:55:26 -04:00
a6d2fb9e31 Change colors. 2025-03-29 10:26:39 -04:00
8b71af06a8 Add placeholders for side panel and console. 2025-03-29 09:55:09 -04:00
22 changed files with 1605 additions and 159 deletions

7
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target
.qtcreator
.idea
**/target/**
**/.qtcreator/**
**/.idea/**
**/*.autosave/**

547
Cargo.lock generated
View File

@ -2,12 +2,65 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "cc"
version = "1.2.16"
@ -19,13 +72,34 @@ dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-format"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "696283b40e1a39d208ee614b92e5f6521d16962edeb47c48372585ec92419943"
dependencies = [
"thiserror",
"thiserror 1.0.69",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags 1.3.2",
"strsim 0.8.0",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
@ -45,7 +119,7 @@ checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
dependencies = [
"anstyle",
"clap_lex",
"strsim",
"strsim 0.11.1",
]
[[package]]
@ -62,6 +136,10 @@ dependencies = [
"cxx-qt",
"cxx-qt-build",
"cxx-qt-lib",
"dirs",
"log",
"structopt",
"syntect",
]
[[package]]
@ -83,6 +161,15 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "cxx"
version = "1.0.148"
@ -106,7 +193,7 @@ dependencies = [
"codespan-reporting",
"proc-macro2",
"quote",
"syn",
"syn 2.0.100",
]
[[package]]
@ -120,7 +207,7 @@ dependencies = [
"cxx-qt-macro",
"qt-build-utils",
"static_assertions",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -152,7 +239,7 @@ dependencies = [
"indoc",
"proc-macro2",
"quote",
"syn",
"syn 2.0.100",
]
[[package]]
@ -175,7 +262,7 @@ checksum = "58a4fe02c0604eda28c605792f5ba0d0251b4947f8f0fc43e55b61c06b2b8ec6"
dependencies = [
"cxx-qt-gen",
"proc-macro2",
"syn",
"syn 2.0.100",
]
[[package]]
@ -184,11 +271,11 @@ version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40399fddbf3977647bfff7453dacffc6b5701b19a282a283369a870115d0a049"
dependencies = [
"clap",
"clap 4.5.32",
"codespan-reporting",
"proc-macro2",
"quote",
"syn",
"syn 2.0.100",
]
[[package]]
@ -206,7 +293,37 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn",
"syn 2.0.100",
]
[[package]]
name = "deranged"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058"
dependencies = [
"powerfmt",
]
[[package]]
name = "dirs"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys",
]
[[package]]
@ -215,12 +332,79 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "flate2"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "indoc"
version = "2.0.6"
@ -251,12 +435,28 @@ dependencies = [
"libc",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.9.0",
"libc",
]
[[package]]
name = "link-cplusplus"
version = "1.0.10"
@ -266,6 +466,18 @@ dependencies = [
"cc",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.4"
@ -278,6 +490,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
"adler2",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -288,6 +509,95 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "onig"
version = "6.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f"
dependencies = [
"bitflags 1.3.2",
"libc",
"once_cell",
"onig_sys",
]
[[package]]
name = "onig_sys"
version = "69.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "plist"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d"
dependencies = [
"base64",
"indexmap",
"quick-xml",
"serde",
"time",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.94"
@ -304,10 +614,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efb239fdd8c036fabb95364320041ef68197cd4ab971bb3b4ca3ea0b7b93d12c"
dependencies = [
"cc",
"thiserror",
"thiserror 1.0.69",
"versions",
]
[[package]]
name = "quick-xml"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.40"
@ -317,6 +636,23 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_users"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
dependencies = [
"getrandom",
"libredox",
"thiserror 2.0.12",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustversion"
version = "1.0.20"
@ -329,6 +665,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "serde"
version = "1.0.219"
@ -346,7 +691,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.100",
]
[[package]]
@ -373,12 +718,53 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "structopt"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
dependencies = [
"clap 2.34.0",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.100"
@ -390,6 +776,28 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syntect"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
dependencies = [
"bincode",
"bitflags 1.3.2",
"flate2",
"fnv",
"once_cell",
"onig",
"plist",
"regex-syntax",
"serde",
"serde_derive",
"serde_json",
"thiserror 1.0.69",
"walkdir",
"yaml-rust",
]
[[package]]
name = "termcolor"
version = "1.4.1"
@ -399,13 +807,31 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.12",
]
[[package]]
@ -416,7 +842,49 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.100",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "time"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
@ -437,6 +905,12 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.5"
@ -453,6 +927,38 @@ dependencies = [
"nom",
]
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.9"
@ -462,6 +968,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.59.0"
@ -534,3 +1046,12 @@ name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

View File

@ -6,7 +6,11 @@ edition = "2024"
[dependencies]
cxx = "1.0.95"
cxx-qt = "0.7"
cxx-qt-lib = { version="0.7", features = ["qt_full"] }
cxx-qt-lib = { version="0.7", features = ["qt_full", "qt_gui"] }
log = { version = "0.4.27", features = [] }
dirs = "6.0.0"
syntect = "5.2.0"
structopt = "0.3.26"
[build-dependencies]
# The link_qt_object_files feature is required for statically linking Qt 6.

View File

@ -5,7 +5,7 @@ CLIDE is an IDE written in Rust that supports both full and headless Linux envir
The following packages must be installed before the application will build.
```bash
sudo apt install qt6-base-dev qt6-declarative-dev qt6-tools-dev qml6-module-qtquick-controls qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick
sudo apt install qt6-base-dev qt6-declarative-dev qt6-tools-dev qml6-module-qtquick-controls qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick qml6-module-qtquick-dialogs qt6-svg-dev
```
And of course, [Rust](https://www.rust-lang.org/tools/install).
@ -22,10 +22,39 @@ The [Qt Installer](https://www.qt.io/download-qt-installer) will provide the lat
If using RustRover be sure to set your QML binaries path in the settings menu.
If Qt was installed to its default directory this will be `$HOME/Qt/6.8.3/gcc_64/bin/`.
Viewing documentation in the web browser is possible, but you will end up in a mess of tabs.
Using Qt Assistant is recommended. It comes with Qt6 when installed. Run the following command to start it.
```bash
nohup $HOME/Qt/6.8.3/gcc_64/bin/assistant > /dev/null 2>&1 &
```
If you are looking for an include path from Qt
```bash
find /usr/include/x86_64-linux-gnu/qt6/ -name QFile*
/usr/include/x86_64-linux-gnu/qt6/QtWidgets/QFileIconProvider
/usr/include/x86_64-linux-gnu/qt6/QtWidgets/QFileDialog
/usr/include/x86_64-linux-gnu/qt6/QtGui/QFileSystemModel
/usr/include/x86_64-linux-gnu/qt6/QtGui/QFileOpenEvent
/usr/include/x86_64-linux-gnu/qt6/QtCore/QFile
/usr/include/x86_64-linux-gnu/qt6/QtCore/QFileDevice
/usr/include/x86_64-linux-gnu/qt6/QtCore/QFileSystemWatcher
/usr/include/x86_64-linux-gnu/qt6/QtCore/QFileInfoList
/usr/include/x86_64-linux-gnu/qt6/QtCore/QFileInfo
/usr/include/x86_64-linux-gnu/qt6/QtCore/QFileSelector
```
This helped find that QFileSystemModel is in QtGui and not QtCore.
### Resources
Some helpful links for reading up on QML if you're just getting started.
* [Rust Crates - cxx-qt](https://docs.rs/releases/search?query=cxx_qt)
* [QML Reference](https://doc.qt.io/qt-6/qmlreference.html)
* [QML Coding Conventions](https://doc.qt.io/qt-6/qml-codingconventions.html)
* [All QML Controls Types](https://doc.qt.io/qt-6/qtquick-controls-qmlmodule.html)
* [KDAB CXX-Qt Book](https://kdab.github.io/cxx-qt/book/)
* [github.com/KDAB/cxx-qt](https://github.com/KDAB/cxx-qt)

View File

@ -8,13 +8,20 @@ fn main() {
// - Qt Qml is linked by enabling the qt_qml Cargo feature of cxx-qt-lib.
// - Qt Qml requires linking Qt Network on macOS
.qt_module("Network")
.qt_module("Gui")
.qt_module("Svg")
.qt_module("Xml")
.qml_module(QmlModule {
uri: "test",
rust_files: &["src/main.rs"],
qml_files: &["qml/main.qml",
"qml/Menu/ClideMenu.qml",
"qml/Menu/ClideMenuBar.qml",
"qml/Menu/ClideMenuBarItem.qml"],
uri: "clide.module",
rust_files: &["src/gui/colors.rs", "src/gui/filesystem.rs"],
qml_files: &[
"qml/main.qml",
"qml/ClideAboutWindow.qml",
"qml/ClideTreeView.qml",
"qml/ClideProjectView.qml",
"qml/ClideEditor.qml",
"qml/ClideMenuBar.qml",
],
..Default::default()
})
.build();

38
icons/folder_closed.svg Normal file
View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
viewBox="-10 0 1792 1792"
id="svg51"
sodipodi:docname="folder_closed.svg"
width="1792"
height="1792"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs55" />
<sodipodi:namedview
id="namedview53"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.45033482"
inkscape:cx="842.70632"
inkscape:cy="896"
inkscape:window-width="1846"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg51" />
<path
fill="currentColor"
d="m 1718,672 v 704 q 0,92 -66,158 -66,66 -158,66 H 278 q -92,0 -158,-66 -66,-66 -66,-158 V 416 q 0,-92 66,-158 66,-66 158,-66 h 320 q 92,0 158,66 66,66 66,158 v 32 h 672 q 92,0 158,66 66,66 66,158 z"
id="path49" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

38
icons/folder_open.svg Normal file
View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
viewBox="-10 0 1792 1792"
id="svg139"
sodipodi:docname="folder_open.svg"
width="1792"
height="1792"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs143" />
<sodipodi:namedview
id="namedview141"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.24358259"
inkscape:cx="149.84651"
inkscape:cy="1098.1901"
inkscape:window-width="1846"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg139" />
<path
fill="currentColor"
d="M 1590,1376 V 672 q 0,-40 -28,-68 -28,-28 -68,-28 H 790 q -40,0 -68,-28 -28,-28 -28,-68 v -64 q 0,-40 -28,-68 -28,-28 -68,-28 H 278 q -40,0 -68,28 -28,28 -28,68 v 960 q 0,40 28,68 28,28 68,28 h 1216 q 40,0 68,-28 28,-28 28,-68 z m 128,-704 v 704 q 0,92 -66,158 -66,66 -158,66 H 278 q -92,0 -158,-66 -66,-66 -66,-158 V 416 q 0,-92 66,-158 66,-66 158,-66 h 320 q 92,0 158,66 66,66 66,158 v 32 h 672 q 92,0 158,66 66,66 66,158 z"
id="path137" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

38
icons/generic_file.svg Normal file
View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
viewBox="-10 0 1792 1792"
id="svg147"
sodipodi:docname="generic_file.svg"
width="1792"
height="1792"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs151" />
<sodipodi:namedview
id="namedview149"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.12179129"
inkscape:cx="-578.85911"
inkscape:cy="1687.3127"
inkscape:window-width="1846"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg147" />
<path
fill="currentColor"
d="m 1586,476 q 14,14 28,36 H 1142 V 40 q 22,14 36,28 z m -476,164 h 544 v 1056 q 0,40 -28,68 -28,28 -68,28 H 214 q -40,0 -68,-28 -28,-28 -28,-68 V 96 Q 118,56 146,28 174,0 214,0 h 800 v 544 q 0,40 28,68 28,28 68,28 z m 160,736 v -64 q 0,-14 -9,-23 -9,-9 -23,-9 H 534 q -14,0 -23,9 -9,9 -9,23 v 64 q 0,14 9,23 9,9 23,9 h 704 q 14,0 23,-9 9,-9 9,-23 z m 0,-256 v -64 q 0,-14 -9,-23 -9,-9 -23,-9 H 534 q -14,0 -23,9 -9,9 -9,23 v 64 q 0,14 9,23 9,9 23,9 h 704 q 14,0 23,-9 9,-9 9,-23 z m 0,-256 v -64 q 0,-14 -9,-23 -9,-9 -23,-9 H 534 q -14,0 -23,9 -9,9 -9,23 v 64 q 0,14 9,23 9,9 23,9 h 704 q 14,0 23,-9 9,-9 9,-23 z"
id="path145" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/kilroy-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

73
qml/ClideAboutWindow.qml Normal file
View File

@ -0,0 +1,73 @@
// TODO: Header
import QtQuick
import QtQuick.Controls.Basic
import clide.module 1.0
ApplicationWindow {
id: root
width: 450
height: 350
// Create the window with no frame and keep it on top.
flags: Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
color: RustColors.gutter
// Hide the window when it loses focus.
onActiveChanged: {
if (!active) {
root.visible = false;
}
}
// Kilroy logo.
Image {
id: logo
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 20
source: "../icons/kilroy-256.png"
sourceSize.width: 80
sourceSize.height: 80
fillMode: Image.PreserveAspectFit
smooth: true
antialiasing: true
asynchronous: true
}
ScrollView {
anchors.top: logo.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 20
TextArea {
selectedTextColor: RustColors.editor_highlighted_text
selectionColor: RustColors.editor_highlight
horizontalAlignment: Text.AlignHCenter
textFormat: Text.RichText
text: qsTr("<h3>About CLIDE</h3>"
+ "<p>A simple text editor written in Rust and QML using CXX-Qt.</p>"
+ "<p>Personal website <a href=\"http://shaunreed.com\">shaunreed.com</a></p>"
+ "<p>Project notes <a href=\"http://knoats.com\">knoats.com</a></p>"
+ "<p>This project is developed at <a href=\"http://git.shaunreed.com/shaunrd0/clide\">git.shaunreed.com</a></p>"
+ "<p><a href=\"https://github.com/KDAB/cxx-qt\">KDAB CXX-Qt repository</a></p>"
+ "<p>Copyright (C) 2025 Shaun Reed, all rights reserved.</p>")
color: RustColors.editor_text
wrapMode: Text.WordWrap
readOnly: true
antialiasing: true
background: null
onLinkActivated: function (link) {
Qt.openUrlExternally(link)
}
}
}
}

206
qml/ClideEditor.qml Normal file
View File

@ -0,0 +1,206 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import clide.module 1.0
SplitView {
id: root
Layout.fillHeight: true
Layout.fillWidth: true
orientation: Qt.Vertical
// The path to the file to show in the text editor.
// This is updated by a signal caught within ClideProjectView.
// Initialized by the Default trait for the Rust QML singleton FileSystem.
required property string filePath;
// Customized handle to drag between the Editor and the Console.
handle: Rectangle {
border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter
color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter
implicitHeight: 8
radius: 2.5
// Execute these behaviors when the color is changed.
Behavior on color {
ColorAnimation {
duration: 400
}
}
}
RowLayout {
// We use a flickable to synchronize the position of the editor and
// the line numbers. This is necessary because the line numbers can
// extend the available height.
Flickable {
id: lineNumbers
Layout.fillHeight: true
Layout.fillWidth: false
// Calculating the width correctly is important as the number grows.
// We need to ensure space required to show N line number digits.
// We use log10 to find how many digits are needed in a line number.
// log10(9) ~= .95; log10(10) = 1.0; log10(100) = 2.0 ...etc
// We +1 to ensure space for at least 1 digit, as floor(1.95) = 1.
// The +10 is additional spacing and can be adjusted.
Layout.preferredWidth: fontMetrics.averageCharacterWidth * (Math.floor(Math.log10(textArea.lineCount)) + 1) + 10
contentY: editorFlickable.contentY
interactive: false
Column {
anchors.fill: parent
topPadding: textArea.topPadding
Repeater {
id: repeatedLineNumbers
// TODO: Bug where text wrapping shows as new line number.
model: textArea.lineCount
// This Item is used for each line number in the gutter.
delegate: Item {
// Calculates the height of each line in the text area.
height: textArea.contentHeight / textArea.lineCount
width: parent.width
required property int index
// Show the line number.
Label {
id: numbers
color: RustColors.linenumber
font: textArea.font
height: parent.height
horizontalAlignment: Text.AlignLeft
text: parent.index + 1
verticalAlignment: Text.AlignVCenter
width: parent.width - indicator.width
}
// Draw edge along the right side of the line number.
Rectangle {
id: indicator
anchors.left: numbers.right
color: RustColors.linenumber
height: parent.height
width: 1
}
}
}
}
}
Flickable {
id: editorFlickable
Layout.fillHeight: true
Layout.fillWidth: true
boundsBehavior: Flickable.StopAtBounds
height: 650
ScrollBar.horizontal: MyScrollBar {
}
ScrollBar.vertical: MyScrollBar {
}
TextArea.flickable: TextArea {
id: textArea
focus: true
persistentSelection: true
antialiasing: true
selectByMouse: true
selectionColor: RustColors.editor_highlight
selectedTextColor: RustColors.editor_highlighted_text
textFormat: Qt.AutoText
wrapMode: TextArea.Wrap
text: FileSystem.readFile(root.filePath)
onLinkActivated: function (link) {
Qt.openUrlExternally(link);
}
// TODO: Handle saving
// Component.onCompleted: {
// if (Qt.application.arguments.length === 2)
// textDocument.source = "file:" + Qt.application.arguments[1]
// else
// textDocument.source = "qrc:/texteditor.html"
// }
// textDocument.onStatusChanged: {
// // a message lookup table using computed properties:
// // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
// const statusMessages = {
// [ TextDocument.ReadError ]: qsTr("Failed to load %1"),
// [ TextDocument.WriteError ]: qsTr("Failed to save %1"),
// [ TextDocument.NonLocalFileError ]: qsTr("Not a local file: %1"),
// }
// const err = statusMessages[textDocument.status]
// if (err) {
// errorDialog.text = err.arg(textDocument.source)
// errorDialog.open()
// }
// }
}
FontMetrics {
id: fontMetrics
font: textArea.font
}
}
}
TextArea {
id: areaConsole
height: 100
placeholderText: qsTr("Placeholder for bash terminal.")
placeholderTextColor: "white"
readOnly: true
wrapMode: TextArea.Wrap
background: Rectangle {
color: RustColors.editor_background
implicitHeight: 100
// border.color: control.enabled ? RustColors.active : RustColors.inactive
}
}
// We use an inline component to customize the horizontal and vertical
// scroll-bars. This is convenient when the component is only used in one file.
component MyScrollBar: ScrollBar {
id: scrollBar
// Scroll bar gutter
background: Rectangle {
implicitHeight: scrollBar.interactive ? 8 : 4
implicitWidth: scrollBar.interactive ? 8 : 4
color: RustColors.scrollbar_gutter
// Fade the scrollbar gutter when inactive.
opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.2
Behavior on opacity {
OpacityAnimator {
duration: 500
}
}
}
// Scroll bar
contentItem: Rectangle {
implicitHeight: scrollBar.interactive ? 8 : 4
implicitWidth: scrollBar.interactive ? 8 : 4
// If we don't need a scrollbar, fallback to the gutter color.
// If the scrollbar is required change it's color based on activity.
color: scrollBar.size < 1.0 ? scrollBar.active ? RustColors.scrollbar_active : RustColors.scrollbar : RustColors.scrollbar_gutter
// Smooth transition between color changes based on the state above.
Behavior on color {
ColorAnimation {
duration: 1000
}
}
// Fade the scrollbar when inactive.
opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.35
Behavior on opacity {
OpacityAnimator {
duration: 500
}
}
}
}
}

View File

@ -1,11 +1,42 @@
import QtQuick
import QtQuick.Controls
import clide.module 1.0
MenuBar {
// Base settings for each Menu.
component ClideMenu : Menu {
background: Rectangle {
color: "#3b3e40" // Dark background like CLion
color: RustColors.menubar
implicitWidth: 100
radius: 2
}
}
// Base settings for each MenuItem.
component ClideMenuItem : MenuItem {
id: root
background: Rectangle {
color: root.hovered ? RustColors.hovered : RustColors.unhovered
radius: 2.5
}
contentItem: IconLabel {
color: "black"
font.family: "Helvetica"
text: root.text
}
}
// Background for this MenuBar.
background: Rectangle {
color: RustColors.menubar
border.color: RustColors.menubar_border
}
//
// File Menu
Action {
id: actionNewProject
@ -25,32 +56,37 @@ MenuBar {
id: actionExit
text: qsTr("&Exit")
onTriggered: Qt.quit()
}
ClideMenu {
title: qsTr("&File")
ClideMenuBarItem {
ClideMenuItem {
action: actionNewProject
}
ClideMenuBarItem {
ClideMenuItem {
action: actionOpen
onTriggered: FileSystem.setDirectory(FileSystem.filePath)
}
ClideMenuBarItem {
ClideMenuItem {
action: actionSave
}
MenuSeparator {
background: Rectangle {
border.color: color
color: "#3c3f41"
color: RustColors.menubar_border
implicitHeight: 3
implicitWidth: 200
}
}
ClideMenuBarItem {
ClideMenuItem {
action: actionExit
}
}
//
// Edit Menu
Action {
id: actionUndo
@ -79,22 +115,25 @@ MenuBar {
ClideMenu {
title: qsTr("&Edit")
ClideMenuBarItem {
ClideMenuItem {
action: actionUndo
}
ClideMenuBarItem {
ClideMenuItem {
action: actionRedo
}
ClideMenuBarItem {
ClideMenuItem {
action: actionCut
}
ClideMenuBarItem {
ClideMenuItem {
action: actionCopy
}
ClideMenuBarItem {
ClideMenuItem {
action: actionPaste
}
}
//
// View Menu
Action {
id: actionToolWindows
@ -108,13 +147,20 @@ MenuBar {
ClideMenu {
title: qsTr("&View")
ClideMenuBarItem {
ClideMenuItem {
action: actionToolWindows
}
ClideMenuBarItem {
ClideMenuItem {
action: actionAppearance
}
}
//
// Help Menu
ClideAboutWindow {
id: clideAbout
}
Action {
id: actionDocumentation
@ -122,16 +168,18 @@ MenuBar {
}
Action {
id: actionAbout
// Toggle the about window with the menu item is clicked.
onTriggered: clideAbout.visible = !clideAbout.visible
text: qsTr("&About")
}
ClideMenu {
title: qsTr("&Help")
ClideMenuBarItem {
ClideMenuItem {
action: actionDocumentation
}
ClideMenuBarItem {
ClideMenuItem {
action: actionAbout
}
}

55
qml/ClideProjectView.qml Normal file
View File

@ -0,0 +1,55 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import clide.module 1.0
SplitView {
id: root
// Path to the file selected in the tree view.
default property string selectedFilePath: FileSystem.filePath;
Layout.fillHeight: true
Layout.fillWidth: true
anchors.fill: parent
// Customized handle to drag between the Navigation and the Editor.
handle: Rectangle {
id: verticalSplitHandle
border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter
color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter
implicitWidth: 8
radius: 2.5
// Execute these behaviors when the color is changed.
Behavior on color {
ColorAnimation {
duration: 400
}
}
}
Rectangle {
id: navigationView
color: RustColors.explorer_background
SplitView.fillHeight: true
SplitView.minimumWidth: 0
SplitView.preferredWidth: 200
SplitView.maximumWidth: 250
StackLayout {
anchors.fill: parent
ClideTreeView {
id: clideTreeView
onFileClicked: path => root.selectedFilePath = path
}
}
}
ClideEditor {
SplitView.fillWidth: true
// Initialize using the Default trait in Rust QML singleton FileSystem.
filePath: root.selectedFilePath
}
}

146
qml/ClideTreeView.qml Normal file
View File

@ -0,0 +1,146 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import clide.module 1.0
Rectangle {
id: root
color: RustColors.explorer_background
signal fileClicked(string filePath)
TreeView {
id: fileSystemTreeView
anchors.margins: 15
// rootIndex: FileSystem.rootIndex
property int lastIndex: -1
model: FileSystem
anchors.fill: parent
boundsBehavior: Flickable.StopAtBounds
boundsMovement: Flickable.StopAtBounds
clip: true
Component.onCompleted: {
FileSystem.setDirectory(FileSystem.filePath)
fileSystemTreeView.expandRecursively(0, 4)
}
// The delegate represents a single entry in the filesystem.
delegate: TreeViewDelegate {
id: treeDelegate
indentation: 8
implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250
implicitHeight: 25
required property int index
required property url filePath
required property string fileName
indicator: Image {
id: directoryIcon
x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation)
anchors.verticalCenter: parent.verticalCenter
source: {
// If the item has children, it's a directory.
if (treeDelegate.hasChildren) {
return treeDelegate.expanded ?
"../icons/folder-open-solid.svg" : "../icons/folder-solid.svg";
}
return "../icons/file-solid.svg"
}
sourceSize.width: 15
sourceSize.height: 15
fillMode: Image.PreserveAspectFit
smooth: true
antialiasing: true
asynchronous: true
}
contentItem: Text {
text: treeDelegate.fileName
color: RustColors.explorer_text
}
background: Rectangle {
// TODO: Fix flickering from color transition on states here.
color: (treeDelegate.index === fileSystemTreeView.lastIndex)
? RustColors.explorer_text_selected
: (hoverHandler.hovered ? RustColors.explorer_hovered : "transparent")
radius: 2.5
opacity: hoverHandler.hovered ? 0.75 : 1.0
Behavior on color {
ColorAnimation {
duration: 300
}
}
}
HoverHandler {
id: hoverHandler
}
TapHandler {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onSingleTapped: (eventPoint, button) => {
switch (button) {
case Qt.LeftButton:
fileSystemTreeView.toggleExpanded(treeDelegate.row)
fileSystemTreeView.lastIndex = treeDelegate.index
// If this model item doesn't have children, it means it's
// representing a file.
if (!treeDelegate.hasChildren)
root.fileClicked(treeDelegate.filePath)
break;
case Qt.RightButton:
if (treeDelegate.hasChildren)
contextMenu.popup();
break;
}
}
}
Menu {
id: contextMenu
Action {
text: qsTr("Set as root index")
onTriggered: {
console.log("Setting directory: " + treeDelegate.filePath)
FileSystem.setDirectory(treeDelegate.filePath)
}
}
Action {
text: qsTr("Reset root index")
onTriggered: {
FileSystem.setDirectory("")
}
}
}
}
// Provide our own custom ScrollIndicator for the TreeView.
ScrollIndicator.vertical: ScrollIndicator {
active: true
implicitWidth: 15
contentItem: Rectangle {
implicitWidth: 6
implicitHeight: 6
color: RustColors.scrollbar
opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0
Behavior on opacity {
OpacityAnimator {
duration: 500
}
}
}
}
}
}

View File

@ -1,10 +0,0 @@
import QtQuick
import QtQuick.Controls
Menu {
background: Rectangle {
color: "#3c3f41"
implicitWidth: 200
radius: 2
}
}

View File

@ -1,16 +0,0 @@
import QtQuick
import QtQuick.Controls
MenuItem {
id: root
background: Rectangle {
color: root.hovered ? "#4b4f51" : "#3c3f41" // Hover effect
radius: 2.5
}
contentItem: IconLabel {
color: "white"
font.family: "Helvetica"
text: root.text
}
}

View File

@ -1,10 +1,13 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import "Menu"
import clide.module 1.0
ApplicationWindow {
id: appWindow
height: 800
title: "CLIDE"
visible: true
@ -15,6 +18,14 @@ ApplicationWindow {
Rectangle {
anchors.fill: parent
color: "#1e1f22" // Dark background
color: RustColors.gutter
}
MessageDialog {
id: errorDialog
title: qsTr("Error")
}
ClideProjectView {
}
}

38
src/gui.rs Normal file
View File

@ -0,0 +1,38 @@
use crate::gui::filesystem::FS_STATE;
use crate::gui::filesystem::qobject::FileSystem;
use cxx_qt_lib::{QModelIndex, QQmlEngine, QString};
use std::error::Error;
pub mod colors;
pub mod filesystem;
pub fn run(root_path: std::path::PathBuf) -> Result<(), Box<dyn Error>> {
println!("Starting the GUI editor at {:?}", root_path);
use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl};
let mut app = QGuiApplication::new();
let mut engine = QQmlApplicationEngine::new();
// let clide_fs = FileSystemImpl {
// file_path: QString::from(root_path.to_str().unwrap()),
// root_index: QModelIndex::default(),
// };
unsafe {
let mut fs_state = FS_STATE.take_fs();
fs_state.file_path = QString::from(root_path.to_str().unwrap());
};
if let Some(engine) = engine.as_mut() {
engine.add_import_path(&QString::from("qml/"));
}
if let Some(engine) = engine.as_mut() {
engine.load(&QUrl::from("qml/main.qml"));
}
if let Some(app) = app.as_mut() {
app.exec();
}
Ok(())
}

92
src/gui/colors.rs Normal file
View File

@ -0,0 +1,92 @@
#[cxx_qt::bridge]
pub mod qobject {
unsafe extern "C++" {
include!("cxx-qt-lib/qcolor.h");
type QColor = cxx_qt_lib::QColor;
}
unsafe extern "RustQt" {
#[qobject]
#[qml_element]
#[qml_singleton]
#[qproperty(QColor, hovered)]
#[qproperty(QColor, unhovered)]
#[qproperty(QColor, pressed)]
#[qproperty(QColor, menubar)]
#[qproperty(QColor, menubar_border)]
#[qproperty(QColor, scrollbar)]
#[qproperty(QColor, scrollbar_active)]
#[qproperty(QColor, scrollbar_gutter)]
#[qproperty(QColor, linenumber)]
#[qproperty(QColor, active)]
#[qproperty(QColor, inactive)]
#[qproperty(QColor, editor_background)]
#[qproperty(QColor, editor_text)]
#[qproperty(QColor, editor_highlighted_text)]
#[qproperty(QColor, editor_highlight)]
#[qproperty(QColor, gutter)]
#[qproperty(QColor, explorer_hovered)]
#[qproperty(QColor, explorer_text)]
#[qproperty(QColor, explorer_text_selected)]
#[qproperty(QColor, explorer_background)]
#[qproperty(QColor, explorer_folder)]
#[qproperty(QColor, explorer_folder_open)]
type RustColors = super::RustColorsImpl;
}
}
use cxx_qt_lib::QColor;
pub struct RustColorsImpl {
hovered: QColor,
unhovered: QColor,
pressed: QColor,
menubar: QColor,
menubar_border: QColor,
scrollbar: QColor,
scrollbar_active: QColor,
scrollbar_gutter: QColor,
linenumber: QColor,
active: QColor,
inactive: QColor,
editor_background: QColor,
editor_text: QColor,
editor_highlighted_text: QColor,
editor_highlight: QColor,
gutter: QColor,
explorer_hovered: QColor,
explorer_text: QColor,
explorer_text_selected: QColor,
explorer_background: QColor,
explorer_folder: QColor,
explorer_folder_open: QColor,
}
impl Default for RustColorsImpl {
fn default() -> Self {
Self {
hovered: QColor::try_from("#303234").unwrap(),
unhovered: QColor::try_from("#3c3f41").unwrap(),
pressed: QColor::try_from("#4b4f51").unwrap(),
menubar: QColor::try_from("#3c3f41").unwrap(),
menubar_border: QColor::try_from("#575757").unwrap(),
scrollbar: QColor::try_from("#4b4f51").unwrap(),
scrollbar_active: QColor::try_from("#4b4f51").unwrap(),
scrollbar_gutter: QColor::try_from("#3b3b3b").unwrap(),
linenumber: QColor::try_from("#94989b").unwrap(),
active: QColor::try_from("#a9acb0").unwrap(),
inactive: QColor::try_from("#FFF").unwrap(),
editor_background: QColor::try_from("#2b2b2b").unwrap(),
editor_text: QColor::try_from("#acaea3").unwrap(),
editor_highlighted_text: QColor::try_from("#ccced3").unwrap(),
editor_highlight: QColor::try_from("#ccced3").unwrap(),
gutter: QColor::try_from("#1e1f22").unwrap(),
explorer_hovered: QColor::try_from("#4c5053").unwrap(),
explorer_text: QColor::try_from("#3b3b3b").unwrap(),
explorer_text_selected: QColor::try_from("#8b8b8b").unwrap(),
explorer_background: QColor::try_from("#676c70").unwrap(),
explorer_folder: QColor::try_from("#54585b").unwrap(),
explorer_folder_open: QColor::try_from("#FFF").unwrap(),
}
}
}

153
src/gui/filesystem.rs Normal file
View File

@ -0,0 +1,153 @@
#[cxx_qt::bridge]
pub mod qobject {
unsafe extern "C++" {
// Import Qt Types from C++
include!("cxx-qt-lib/qstring.h");
type QString = cxx_qt_lib::QString;
include!("cxx-qt-lib/qmodelindex.h");
type QModelIndex = cxx_qt_lib::QModelIndex;
include!(<QtGui/QFileSystemModel>);
type QFileSystemModel;
}
#[auto_cxx_name]
#[auto_rust_name]
unsafe extern "RustQt" {
// Export QML Types from Rust
#[qobject]
#[base = QFileSystemModel]
#[qml_element]
#[qml_singleton]
#[qproperty(QString, file_path]
#[qproperty(QModelIndex, root_index]
type FileSystem = super::FileSystemImpl;
#[inherit]
fn set_root_path(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex;
#[qinvokable]
#[cxx_override]
fn column_count(self: &FileSystem, _index: &QModelIndex) -> i32;
#[qinvokable]
fn read_file(self: &FileSystem, path: &QString) -> QString;
#[qinvokable]
fn set_directory(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex;
#[qinvokable]
fn take_fs(self: &mut) -> FileSystem;
}
}
use cxx_qt_lib::{QModelIndex, QString};
use dirs;
use log::warn;
use std::fs;
use std::io::BufRead;
use std::ptr::replace;
use syntect::easy::HighlightFile;
use syntect::highlighting::ThemeSet;
use syntect::html::{
IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet,
};
use syntect::parsing::SyntaxSet;
/// Singleton holding filesystem state.
pub struct FileSystemState {
pub fs: Option<FileSystemImpl>,
}
impl FileSystemState {
pub unsafe fn take_fs(&mut self) -> FileSystemImpl {
let fs = replace(&mut self.fs, None);
fs.unwrap()
}
}
pub static mut FS_STATE : FileSystemState = FileSystemState { fs: Some(FileSystemImpl::default()) };
// TODO: Implement a provider for QFileSystemModel::setIconProvider for icons.
pub struct FileSystemImpl {
pub file_path: QString,
pub root_index: QModelIndex,
}
// Default is explicit to make the editor open this source file initially.
impl Default for FileSystemImpl {
fn default() -> Self {
Self {
file_path: QString::from(file!()),
root_index: Default::default(),
}
}
}
impl qobject::FileSystem {
fn read_file(&self, path: &QString) -> QString {
if path.is_empty() {
return QString::default();
}
if !fs::metadata(path.to_string())
.expect(format!("Failed to get file metadata {}", path).as_str())
.is_file()
{
warn!("Attempted to open file {} that is not a valid file", path);
return QString::default();
}
let ss = SyntaxSet::load_defaults_nonewlines();
let ts = ThemeSet::load_defaults();
let theme = &ts.themes["base16-ocean.dark"];
let mut highlighter =
HighlightFile::new(path.to_string(), &ss, theme).expect("Failed to create highlighter");
let (mut output, _bg) = start_highlighted_html_snippet(theme);
let mut line = String::new();
while highlighter
.reader
.read_line(&mut line)
.expect("Failed to read file.")
> 0
{
let regions = highlighter
.highlight_lines
.highlight_line(&line, &ss)
.expect("Failed to highlight");
append_highlighted_html_for_styled_line(
&regions[..],
IncludeBackground::Yes,
&mut output,
)
.expect("Failed to insert highlighted html");
line.clear();
}
output.push_str("</pre>\n");
QString::from(output)
}
// There will never be more than one column.
fn column_count(&self, _index: &QModelIndex) -> i32 {
1
}
fn set_directory(self: std::pin::Pin<&mut Self>, path: &QString) -> QModelIndex {
if !path.is_empty()
&& fs::metadata(path.to_string())
.expect(format!("Failed to get metadata for path {}", path).as_str())
.is_dir()
{
self.set_root_path(path)
} else {
// If the initial directory can't be opened, attempt to find the home directory.
self.set_root_path(&QString::from(
dirs::home_dir()
.expect("Failed to get home directory")
.as_path()
.to_str()
.unwrap()
.to_string(),
))
}
}
}

View File

@ -1,90 +1,58 @@
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// SPDX-FileContributor: Leon Matthes <leon.matthes@kdab.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
// TODO: Header
#[cxx_qt::bridge]
mod qobject {
unsafe extern "C++" {
include!("cxx-qt-lib/qstring.h");
type QString = cxx_qt_lib::QString;
use std::error::Error;
use std::process::{Command, Stdio};
use structopt::StructOpt;
pub mod gui;
pub mod tui;
/// Command line interface IDE with full GUI and headless modes.
/// If no flags are provided the GUI editor is launched in a separate process.
/// If no path is provided the current directory is used.
#[derive(StructOpt, Debug)]
#[structopt(name = "clide", verbatim_doc_comment)]
struct Cli {
/// The root directory for the project to open with the clide editor.
#[structopt(parse(from_os_str))]
pub path: Option<std::path::PathBuf>,
/// Run clide in headless mode.
#[structopt(name = "tui", short, long)]
pub tui: bool,
/// Run the clide GUI in the current process, blocking the terminal and showing all output streams.
#[structopt(name = "gui", short, long)]
pub gui: bool,
}
#[qenum(Greeter)]
pub enum Language {
English,
German,
French,
}
fn main() -> Result<(), Box<dyn Error>> {
let args = Cli::from_args();
#[qenum(Greeter)]
pub enum Greeting {
Hello,
Bye,
}
let root_path = match args.path {
// If the CLI was provided a directory convert it to absolute.
Some(path) => std::path::absolute(path)?,
// If no path was provided, use current directory.
None => std::env::current_dir().unwrap_or_else(|_|
// If we can't find the CWD attempt to open the home directory.
dirs::home_dir().expect("Failed to access filesystem.")),
};
unsafe extern "RustQt" {
#[qobject]
#[qml_element]
#[qproperty(Greeting, greeting)]
#[qproperty(Language, language)]
type Greeter = super::GreeterRust;
#[qinvokable]
fn greet(self: &Greeter) -> QString;
}
}
use qobject::*;
impl Greeting {
fn translate(&self, language: Language) -> String {
match (self, language) {
(&Greeting::Hello, Language::English) => "Hello, World!",
(&Greeting::Hello, Language::German) => "Hallo, Welt!",
(&Greeting::Hello, Language::French) => "Bonjour, le monde!",
(&Greeting::Bye, Language::English) => "Bye!",
(&Greeting::Bye, Language::German) => "Auf Wiedersehen!",
(&Greeting::Bye, Language::French) => "Au revoir!",
_ => "🤯",
}
.to_string()
}
}
pub struct GreeterRust {
greeting: Greeting,
language: Language,
}
impl Default for GreeterRust {
fn default() -> Self {
Self {
greeting: Greeting::Hello,
language: Language::English,
}
}
}
use cxx_qt_lib::QString;
impl qobject::Greeter {
fn greet(&self) -> QString {
QString::from(self.greeting.translate(self.language))
}
}
fn main() {
use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl};
let mut app = QGuiApplication::new();
let mut engine = QQmlApplicationEngine::new();
if let Some(engine) = engine.as_mut() {
engine.load(&QUrl::from("qml/main.qml"));
}
if let Some(app) = app.as_mut() {
app.exec();
match args.gui {
true => gui::run(root_path),
false => match args.tui {
// Open the TUI editor if requested, otherwise use the QML GUI by default.
true => tui::run(root_path),
false => {
// Relaunch the CLIDE GUI in a separate process.
Command::new(std::env::current_exe()?)
.args(&["--gui", root_path.to_str().unwrap()])
.stdout(Stdio::null())
.stderr(Stdio::null())
.stdin(Stdio::null())
.spawn()?;
Ok(())
}
},
}
}

6
src/tui.rs Normal file
View File

@ -0,0 +1,6 @@
use std::error::Error;
pub fn run(root_path: std::path::PathBuf) -> Result<(), Box<dyn Error>> {
println!("Starting the TUI editor at {:?}", root_path);
Ok(())
}