From 7f472f9dd75470dc7ea424180758b581321e6cf0 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:01:52 +0700
Subject: [PATCH 01/43] Update supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 92 ++++++++++++++--------------------------------------
1 file changed, 25 insertions(+), 67 deletions(-)
diff --git a/supernano.py b/supernano.py
index 46a5037..00dba00 100644
--- a/supernano.py
+++ b/supernano.py
@@ -15,6 +15,7 @@
ModuleInspector,
create_file_or_folder,
resolve_relative_path_v2,
+ resolve_relative_path,
all_system_paths,
)
except:
@@ -30,6 +31,7 @@
ModuleInspector,
create_file_or_folder,
resolve_relative_path_v2,
+ resolve_relative_path,
all_system_paths,
)
except:
@@ -44,6 +46,7 @@
ModuleInspector,
create_file_or_folder,
resolve_relative_path_v2,
+ resolve_relative_path,
all_system_paths,
)
@@ -144,66 +147,6 @@ class PlainButton(urwid.Button):
button_right = urwid.Text("")
-class EditableButton(urwid.WidgetWrap):
- def __init__(self, label, on_save):
- self.label = label
- self.on_save = on_save
- self.button = PlainButton(label)
- self.last_click_time = 0
- urwid.connect_signal(self.button, "click", self.on_click)
- self._w = self.button
-
- def on_click(self, button):
- current_time = time.time()
- # Check for double click within 0.3 seconds
- if current_time - self.last_click_time < 0.3:
- self.edit_text(button)
- self.last_click_time = current_time
-
- def edit_text(self, button):
- self.edit = urwid.Edit(multiline=False, edit_text=self.label)
- urwid.connect_signal(self.edit, "change", self.on_change)
- self._w = urwid.AttrMap(self.edit, None, focus_map="reversed")
-
- def on_change(self, edit, new_text):
- self.label = new_text
-
- def keypress(self, size, key):
- if isinstance(self._w.base_widget, urwid.Edit):
- if key == "enter":
- self.save_and_restore()
- else:
- return self._w.keypress(size, key)
- else:
- return super().keypress(size, key)
-
- def save_and_restore(self):
- self.on_save(self.label)
- self.button = PlainButton(self.label)
- urwid.connect_signal(self.button, "click", self.on_click)
- self._w = self.button
-
-
-class ClipboardTextBox(urwid.Edit):
- def keypress(self, size, key):
- if key == "ctrl c":
- self.copy_to_clipboard()
- elif key == "ctrl v":
- self.paste_from_clipboard()
- else:
- return super().keypress(size, key)
-
- def copy_to_clipboard(self):
- self.clipboard = self.get_edit_text()
-
- def paste_from_clipboard(self):
- if hasattr(self, "clipboard"):
- cursor_pos = self.edit_pos
- text = self.get_edit_text()
- self.set_edit_text(text[:cursor_pos] + self.clipboard + text[cursor_pos:])
- self.edit_pos = cursor_pos + len(self.clipboard)
-
-
class SuperNano:
"""
Kelas SuperNano yang sedang Anda kembangkan adalah text editor berbasis console yang menggunakan Python 3.6 ke atas dengan dukungan urwid[curses].
@@ -225,6 +168,7 @@ def __init__(self, start_path="."):
self.undo_stack, self.redo_stack = [[], []] # Stack for undo # Stack for redo
self.overlay_POPUP = None # Overlay untuk popup
self.module_package_Python = ModuleInspector() # memuat module python
+ self.module_package_PythonC = self.module_package_Python.curents
# Set title
setTitle("Win-SuperNano v{version}".format(version=__version__))
@@ -244,7 +188,7 @@ def create_button(module_name):
self.listmodules_from_package_Python = urwid.SimpleFocusListWalker(
[
create_button(module)
- for module in self.module_package_Python.get_module(sys.path)
+ for module in self.module_package_Python.get_python_module(sys.path)
]
)
# Footer text and ListBox for scrolling
@@ -523,8 +467,8 @@ def get_file_list(self):
urwid.connect_signal(button, "click", self.go_up_directory)
files.append(urwid.AttrMap(button, None, focus_map="reversed"))
- for f in os.listdir(self.current_path):
- if os.path.isdir(os.path.join(self.current_path, f)):
+ for f in os.listdir(f"{self.current_path}"):
+ if os.path.isdir(resolve_relative_path(self.current_path, f)):
f = f + "/"
button = PlainButton(f)
urwid.connect_signal(button, "click", self.open_file, f)
@@ -646,7 +590,7 @@ def go_up_directory(self, button):
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def open_file(self, button, file_name):
"Membuka file yang dipilih, membaca isinya, dan menampilkannya di text editor. Jika itu adalah direktori, berpindah ke direktori tersebut."
- file_path = os.path.join(self.current_path, file_name)
+ file_path = resolve_relative_path(self.current_path, file_name)
_c, ext = os.path.splitext(file_path)
if os.path.isdir(file_path):
if validate_folder(file_path):
@@ -671,12 +615,26 @@ def open_file(self, button, file_name):
content = content.replace("\t", " " * 4)
self.undo_stack.append(content)
self.text_editor.set_edit_text(content)
- self.current_file_name = file_name # Track the current file name
+ if self.current_file_name != file_path:
+ modulefile = self.module_package_Python.get_moduleV2(file_path)
+ if modulefile:
+ if modulefile != self.module_package_Python.curents:
+ self.listmodules_from_package_Python[
+ :
+ ] = self.create_modules_menus(modulefile)
+ self.module_package_Python.curents = modulefile
+ else:
+ self.listmodules_from_package_Python[
+ :
+ ] = self.create_modules_menus(self.module_package_PythonC)
+
+ self.current_file_name = file_name # Track the current file name
# if str(ext).lower() in ( ".pyx", ".pyz", ".py"):
# self.listmodules_from_package_Python[:] = self.modules_menus(self.current_path)
-
- self.main_layout.body.contents[1][0].set_title(file_name)
+ if self.module_package_Python.languages:
+ self.Inspect_modules_from_package_Python.body.set_title(self.module_package_Python.languages["languages"]+" Modules")
+ self.main_layout.body.contents[2][0].set_title(file_name)
else:
self.status_msg_footer_text.set_text("File access denied!")
From d4c6eda77a63875fa9f7374988232e89a8088b1f Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:02:54 +0700
Subject: [PATCH 02/43] Add files via upload
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
libs/ParserNodeModule.js | 118 +++++++++++++++++++++++++++++++++++++++
libs/ParserPHP.js | 72 ++++++++++++++++++++++++
2 files changed, 190 insertions(+)
create mode 100644 libs/ParserNodeModule.js
create mode 100644 libs/ParserPHP.js
diff --git a/libs/ParserNodeModule.js b/libs/ParserNodeModule.js
new file mode 100644
index 0000000..9caa65e
--- /dev/null
+++ b/libs/ParserNodeModule.js
@@ -0,0 +1,118 @@
+const acorn = require('acorn');
+const fs = require('fs');
+
+function analyzeFile(filePath) {
+ const code = fs.readFileSync(filePath, 'utf-8');
+ const syntax = acorn.parse(code, { ecmaVersion: 2020, sourceType: 'module' });
+
+
+ const result = {
+ "module": "",
+ "classes": [],
+ "functions": [],
+ "variables": [],
+ }
+
+ // Recursive function to traverse AST nodes
+ function walk(node) {
+ function extractParameters(params) {
+ return params.map(param => param?.name || param?.params || '');
+ }
+ if (node.type === 'ClassDeclaration' && node.id) {
+ const classInfo = {
+ name: node.id.name,
+ variables: [],
+ functions: []
+ };
+
+ // Collect variables and methods within the class body
+ node.body.body.forEach(member => {
+ if (member.type === 'MethodDefinition' && member.key) {
+ if (member.key.name) {
+ const methodName = member.key.name;
+ const methodType = member.static ? 'static' : 'instance';
+ var parameters
+ if (member.value.params){
+ parameters = extractParameters(member.value.params)
+ }else{
+ parameters = [""]
+ }
+ classInfo.functions.push({ name: `${methodName}`, params: parameters, type: methodType });
+ }
+ } else if (member.type === 'ClassProperty' && member.key) {
+ if (member.key.name) {
+ classInfo.variables.push(member.key.name);
+ }
+ }
+ });
+
+ result.classes.push(classInfo);
+ } else if (node.type === 'FunctionDeclaration' && node.id) {
+ if (node.id?.name!=undefined){
+ var parameters
+ if (node.params){
+ parameters = extractParameters(node.params);
+ }
+ result.functions.push({
+ name: `${node.id.name}`,
+ params: parameters
+ });
+ }
+ }else if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
+ if (node.id?.name!=undefined){
+ var parameters
+ if (node.params){
+ parameters = extractParameters(node.params);
+ }else{
+ parameters = ""
+ }
+ result.functions.push({
+ name: `${node.id.name}`,
+ params: parameters
+ });
+ }
+ }else if (node.type === 'MethodDefinition' && node.key) {
+ if (node.id?.name!=undefined) {
+ var parameters
+ if (node.params){
+ parameters = extractParameters(node.params);
+ }else{
+ parameters = ""
+ }
+ result.functions.push({
+ name: `${node.id.name}`,
+ params: parameters
+ });
+ }
+ }else if (node.type === 'VariableDeclarator' && node.id) {
+ if (node.id?.name!=undefined){
+ result.variables.push(node.id.name);
+ }
+ }
+
+ // Traverse child nodes
+ for (let key in node) {
+ if (node[key] && typeof node[key] === 'object') {
+ walk(node[key]);
+ }
+ }
+ }
+ // Start walking from the top-level nodes
+ walk(syntax);
+ //const functionMap = new Map();
+ //functions.forEach(func => {
+ //if (functionMap.has(func.name)) {
+ // func.type = 'class method';
+ //} else {
+ // func.type = 'function';
+ //}
+ //});
+ return result;//JSON.stringify({"classes":classes, "functions":functions, "variables":variables });
+}
+
+
+
+// Contoh penggunaan
+const filePath = process.argv[2];
+const inspectionResult = analyzeFile(filePath);
+console.log(JSON.stringify(inspectionResult, null, 2));
\ No newline at end of file
diff --git a/libs/ParserPHP.js b/libs/ParserPHP.js
new file mode 100644
index 0000000..793c41b
--- /dev/null
+++ b/libs/ParserPHP.js
@@ -0,0 +1,72 @@
+const fs = require('fs');
+const parser = require('php-parser');
+
+function inspectPHP(filePath) {
+ const code = fs.readFileSync(filePath, 'utf-8');
+ const phpParser = new parser.Engine({
+ parser: {
+ extractDoc: true,
+ suppressErrors: true,
+ },
+ });
+
+ const ast = phpParser.parseCode(code);
+
+ const result = {
+ "module": "",
+ "classes": [],
+ "functions": [],
+ "variables": [],
+ };
+
+ function getFunctionParams(params) {
+ return params.map(param => param.name ? param.name.name : '');
+ }
+
+ function traverse(node) {
+ if (node.kind === 'class') {
+ const classInfo = {
+ name: node.name.name,
+ functions: [],
+ variables: [],
+ };
+
+ node.body.forEach(child => {
+ if (child.kind === 'method') {
+ const functions = {
+ name: child.name.name,
+ params: getFunctionParams(child.arguments),
+ };
+ classInfo.functions.push(functions);
+ } else if (child.kind === 'property') {
+ child.properties.forEach(prop => {
+ classInfo.variables.push(prop.name.name);
+ });
+ }
+ });
+
+ result.classes.push(classInfo);
+ } else if (node.kind === 'function') {
+ const functionInfo = {
+ name: node.name.name,
+ params: getFunctionParams(node.arguments),
+ };
+ result.functions.push(functionInfo);
+ } else if (node.kind === 'variable') {
+ result.variables.push(node.name.name);
+ }
+
+ if (node.children) {
+ node.children.forEach(traverse);
+ }
+ }
+
+ ast.children.forEach(traverse);
+
+ return result;
+}
+
+// Path ke file PHP
+const filePath = process.argv[2];
+const inspectionResult = inspectPHP(filePath);
+console.log(JSON.stringify(inspectionResult, null, 2));
\ No newline at end of file
From 0260b975e57acf7760589fe9f7dc63fc18dd0330 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:11:00 +0700
Subject: [PATCH 03/43] Add Inspect Module C, PYTHON, PHP, C
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
libs/filemanager.py | 476 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 451 insertions(+), 25 deletions(-)
diff --git a/libs/filemanager.py b/libs/filemanager.py
index a920820..d420ecd 100644
--- a/libs/filemanager.py
+++ b/libs/filemanager.py
@@ -1,4 +1,4 @@
-import os, sys, time, shutil, psutil, inspect, importlib, pkg_resources, pkgutil, json, logging, threading
+import os, sys, time, shutil, psutil, inspect, importlib, pkg_resources, pkgutil, json, logging, threading, re
try:
from .helperegex import (
@@ -9,9 +9,11 @@
clean_string,
rreplace,
cleanstring,
+ split_from_right_with_regex,
)
from .cmd_filter import filter_json, safe_load_json
from .system_manajemen import set_low_priority, SafeProcessExecutor
+ from .commandcheck import subprocess
from .timeout import timeout_v1, timeout_v2
from .https import Fetch
except:
@@ -24,9 +26,11 @@
clean_string,
rreplace,
cleanstring,
+ split_from_right_with_regex,
)
from cmd_filter import filter_json, safe_load_json
from system_manajemen import set_low_priority, SafeProcessExecutor
+ from commandcheck import subprocess
from timeout import timeout_v1, timeout_v2
from https import Fetch
except:
@@ -38,9 +42,11 @@
clean_string,
rreplace,
cleanstring,
+ split_from_right_with_regex,
)
from libs.cmd_filter import filter_json, safe_load_json
from libs.system_manajemen import set_low_priority, SafeProcessExecutor
+ from libs.commandcheck import subprocess
from libs.timeout import timeout_v1, timeout_v2
from libs.https import Fetch
@@ -48,6 +54,19 @@
set_low_priority(os.getpid())
+
+def removeduplicatejson(my_list:list):
+ # Menggunakan dictionary untuk melacak elemen unik
+ seen = {}
+ for d in my_list:
+ key = (d["name"]) # Kunci unik berdasarkan 'name' dan 'age'
+ if key not in seen:
+ seen[key] = d
+
+ # Mengambil nilai dari dictionary
+ unique_list = list(seen.values())
+ return list(unique_list)
+
script_dir = os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
all_system_paths = ["/".join(script_dir.split("/")[:-1]), script_dir]
@@ -134,38 +153,150 @@ def close(self):
class ModuleInspector:
def __init__(self):
+
+ self.languages = {"languages": "PYTHON"}
self.modules = self.getsys_module()
self.curents = self.modules
self.curentpath = sys.path.copy()
self.modulepathnow = []
- def getsys_module(self):
- return sorted(
- [
- module.name
- for module in pkgutil.iter_modules([x for x in sys.path if x])
- if not module.name.strip().startswith("~")
- and not module.name.strip().startswith("__pycache__")
- ]
+ def get_two_level_subfolders(self, root_directory):
+ def is_valid_directory(directory_name):
+ # Memeriksa apakah nama direktori mengandung spasi atau karakter khusus
+ if re.search(r'[ \s@#$%^&*()+=\[\]{};:\'",<>?/\\|`~]', directory_name):
+ return False
+ return True
+
+ result = []
+ for root, dirs, files in os.walk(root_directory):
+ # Mendapatkan subfolder pada level pertama
+ if root == root_directory:
+ result.extend(
+ [
+ resolve_relative_path(root, d).replace("\\", "/")
+ for d in dirs
+ if d.count(" ") == 0 and is_valid_directory(d)
+ ]
+ )
+ else:
+ # Mendapatkan subfolder pada level kedua
+ # Hentikan iterasi lebih dalam dengan mengosongkan 'dirs'
+ dirs[:] = []
+ result.extend(
+ [
+ resolve_relative_path(root, d).replace("\\", "/")
+ for d in dirs
+ if d.count(" ") == 0 and is_valid_directory(d)
+ ]
+ )
+ if root_directory.replace("\\", "/") not in result:
+ result.append(root_directory.replace("\\", "/"))
+ return result
+
+ def subprocess(self, filescript: str, file_path: str):
+ try:
+ result = subprocess.run(
+ ["node", resolve_relative_path(all_system_paths[1], filescript), file_path], capture_output=True, text=True
+ )
+
+ if result.returncode == 0:
+ # Parsing output JSON
+ return json.loads(result.stdout)
+ else:
+ return {}
+ except:
+ return {}
+
+ ########################NodeJS module
+ def get_global_nodejs_modules(self):
+ # Menemukan path global `node_modules`
+ node_modules_path = os.path.join(
+ os.getenv("APPDATA") or "/usr/local", "npm", "node_modules"
)
-
- def get_module(self, paths:list=[]):
- def getmodules(path:list, result:list):
- result.extend(sorted(
+
+ # Mengambil semua modul di direktori
+ try:
+ self.languages["languages"] = "NODEJS"
+ return [
+ module
+ for module in os.listdir(node_modules_path)
+ if not module.startswith(".")
+ ] or None
+ except FileNotFoundError:
+ return []
+
+ def inspect_nodejs_module(self, module_name):
+ try:
+ # Path ke node_modules global
+ node_modules_path = os.path.join(
+ os.getenv("APPDATA") or "/usr/local", "npm", "node_modules"
+ )
+ module_path = resolve_relative_path(node_modules_path, module_name)
+
+ # Path ke package.json
+ package_json_path = os.path.join(module_path, "package.json")
+ if not os.path.isfile(package_json_path):
+ return None
+ # Membaca file package.json
+ try:
+ with open(package_json_path, "r") as f:
+ package_info = json.load(f)
+ except:
+ return None
+ # Mendapatkan file entry point utama dari package.json
+ entry_point = package_info.get("main", "index.js")
+ # Path lengkap ke entry point
+ index_file_path = resolve_relative_path(module_path, entry_point)
+
+ # Pastikan file tersebut ada
+ if not os.path.isfile(index_file_path):
+ return None
+
+ result = self.subprocess("ParserNodeModule.js", index_file_path)
+ if "module" in result.keys():
+ result.update({"module": module_name})
+ return result
+
+ except FileNotFoundError:
+ return {}
+
+ ###############################################################
+
+ # -------------------------------------------------------------
+
+ ########################Python module
+ def getsys_module(self):
+ self.languages["languages"] = "PYTHON"
+ return (
+ sorted(
[
module.name
- for module in pkgutil.iter_modules(path)
+ for module in pkgutil.iter_modules([x for x in sys.path if x])
if not module.name.strip().startswith("~")
and not module.name.strip().startswith("__pycache__")
]
- ))
+ )
+ or None
+ )
+
+ def get_python_module(self, paths: list = []):
+ def getmodules(path: list, result: list):
+ result.extend(
+ sorted(
+ [
+ module.name
+ for module in pkgutil.iter_modules(path)
+ if not module.name.strip().startswith("~")
+ and not module.name.strip().startswith("__pycache__")
+ ]
+ )
+ )
threads, result = [[], self.curents]
- if paths.__len__()<1:
- paths = [os.getcwd()]
+ if paths.__len__() < 1:
+ paths = [os.getcwd()]
else:
pass
-
for path in paths:
thread = threading.Thread(target=getmodules, args=([path], result))
@@ -176,8 +307,7 @@ def getmodules(path:list, result:list):
thread.join()
self.modulepathnow = paths
- return result
-
+ return result or None
def list_classes(self, module):
try:
@@ -205,6 +335,17 @@ def get_class_details(self, cls):
return details
+ def get_function_detail(self, module):
+ details = []
+ try:
+ for name, obj in inspect.getmembers(importlib.import_module(module)):
+ if inspect.isfunction(obj):
+ func_details = {"name": name, "params": str(inspect.signature(obj))}
+ details.append(func_details)
+ except:
+ pass
+ return details
+
def get_global_variables(self, module):
try:
imported_module = importlib.import_module(module)
@@ -229,17 +370,17 @@ def serialize_value(self, value):
else:
return str(value) # Convert other types to string
- def inspect_module(self, module_name):
- if self.modulepathnow.__len__()>=1:
+ def inspect_python_module(self, module_name: str):
+ if self.modulepathnow.__len__() >= 1:
sys.path.extend(self.modulepathnow)
self.modulepathnow = []
try:
classes = self.list_classes(module_name)
- global_vars = self.get_global_variables(module_name)
result = {
"module": module_name,
- "global_variables": global_vars,
+ "variables": self.get_global_variables(module_name),
"classes": [],
+ "functions": self.get_function_detail(module_name),
}
for cls in classes:
@@ -254,6 +395,292 @@ def inspect_module(self, module_name):
sys.path = self.curentpath
return None
+ ########################PHP module
+ def get_php_module(self, directory: str):
+ """Menemukan semua file .php dalam direktori."""
+ php_files = []
+ directory = directory.replace("\\", "/")
+ for folder in self.get_two_level_subfolders(directory):
+ for file in os.listdir(folder):
+ if os.path.isfile(resolve_relative_path(folder, file)):
+ index_file_path = resolve_relative_path(folder, file).replace(
+ "\\", "/"
+ )
+ if index_file_path.endswith(".php") or index_file_path.endswith(
+ ".phpx"
+ ):
+ modules = [x for x in index_file_path.split(directory) if x]
+ php_files.append(modules[0])
+ self.languages["languages"] = "PHP"
+ return php_files or None
+
+ def inspect_php_module(self, file_path: str):
+ try:
+ result = self.subprocess("ParserPHP.js", file_path)
+ if "module" in result.keys():
+ head, tail = os.path.split(file_path)
+ result.update({"module": tail})
+ except:
+ result = {}
+ return result
+
+ ########################C module
+ def get_c_module(self, directory: str):
+ """Menemukan semua file .c dan .h dalam direktori."""
+ c_files = []
+ directory = directory.replace("\\", "/")
+ for folder in self.get_two_level_subfolders(directory):
+ for file in os.listdir(folder):
+ if os.path.isfile(resolve_relative_path(folder, file)):
+ index_file_path = resolve_relative_path(folder, file).replace(
+ "\\", "/"
+ )
+ if (
+ index_file_path.endswith(".c")
+ or index_file_path.endswith(".cpp")
+ or index_file_path.endswith("csx")
+ or index_file_path.endswith(".h")
+ ):
+ modules = [x for x in index_file_path.split(directory) if x]
+ c_files.append(modules[0])
+ self.languages["languages"] = "C"
+ return c_files or None
+
+ def inspect_c_module(self, file_path: str):
+ # Menyimpan hasil inspeksi
+ result = {"classes": [], "functions": [], "variables": []}
+ try:
+ import clang.cindex
+
+ def get_functions(node):
+ """Mengambil informasi tentang fungsi dari node AST."""
+ functions = []
+ for child in node.get_children():
+ if child.kind == clang.cindex.CursorKind.FUNCTION_DECL:
+ func_info = {
+ "name": child.spelling,
+ "params": [
+ param.spelling for param in child.get_arguments()
+ ],
+ }
+ functions.append(func_info)
+ elif child.kind == clang.cindex.CursorKind.CXX_METHOD:
+ # Menangani metode dalam kelas (C++)
+ func_info = {
+ "name": child.spelling,
+ "params": [
+ param.spelling for param in child.get_arguments()
+ ],
+ }
+ functions.append(func_info)
+ elif child.kind in [
+ clang.cindex.CursorKind.STRUCT_DECL,
+ clang.cindex.CursorKind.CLASS_DECL,
+ ]:
+ # Menangani struct atau class
+ # Tidak melakukan apa-apa di sini, akan diproses di tempat lain
+ pass
+ return functions
+
+ def get_variables(node):
+ """Mengambil informasi tentang variabel dari node AST."""
+ variables = []
+ for child in node.get_children():
+ if child.kind in [
+ clang.cindex.CursorKind.VAR_DECL,
+ clang.cindex.CursorKind.FIELD_DECL,
+ ]:
+ variables.append(child.spelling)
+ return variables
+
+ index = clang.cindex.Index.create()
+ translation_unit = index.parse(file_path)
+
+ # Menangani fungsi global dan variabel
+ result["functions"].extend(get_functions(translation_unit.cursor))
+ result["variables"].extend(get_variables(translation_unit.cursor))
+
+ # Menangani struct atau class
+ for child in translation_unit.cursor.get_children():
+ if child.kind in [
+ clang.cindex.CursorKind.STRUCT_DECL,
+ clang.cindex.CursorKind.CLASS_DECL,
+ ]:
+ class_info = {
+ "name": child.spelling,
+ "methods": get_functions(child),
+ "variables": get_variables(child),
+ }
+ result["classes"].append(class_info)
+ except:
+ with open(file_path, "r+", encoding=sys.getfilesystemencoding()) as f:
+ code = f.read()
+ # Regex pattern untuk menemukan fungsi dan variabel
+ function_pattern = re.compile(
+ r"\b[A-Za-z_][A-Za-z_0-9]*\s+\**\s*([A-Za-z_][A-Za-z_0-9]*)\s*\(.*?\)\s*\{",
+ re.MULTILINE | re.DOTALL,
+ )
+ variable_pattern = re.compile(
+ r"\b[A-Za-z_][A-Za-z_0-9]*\s+\**\s*([A-Za-z_][A-Za-z_0-9]*)\s*(?=\=|;)",
+ re.MULTILINE | re.DOTALL,
+ )
+
+ # Regex pattern untuk menemukan struct dan field-nya
+ struct_pattern = re.compile(
+ r"\bstruct\s+(\w+)\s*\{([^}]*)\};", re.MULTILINE | re.DOTALL
+ )
+ field_pattern = re.compile(
+ r"([a-zA-Z_][a-zA-Z_0-9]*)\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*(?:\[\s*\d*\s*\])?\s*(?:;|,)",
+ re.MULTILINE | re.DOTALL,
+ )
+ try:
+ # Menemukan fungsi
+ for match in function_pattern.finditer(code):
+ result["functions"].append(
+ {
+ "name": match.group(1),
+ "params": [
+ ""
+ ], # Parameter tidak diekstrak dalam regex sederhana ini
+ }
+ )
+ except:
+ function_pattern = re.compile(
+ r"\b[A-Za-z_][A-Za-z_0-9]*\s+\**\s*([A-Za-z_][A-Za-z_0-9]*)\s*\(.*?\)\s*\{",
+ re.MULTILINE | re.DOTALL,
+ )
+ if function_pattern.finditer(code):
+ for match in function_pattern.finditer(code):
+ result["functions"].append(
+ {
+ "name": match.group(1),
+ "params": [
+ ""
+ ], # Parameter tidak diekstrak dalam regex sederhana ini
+ }
+ )
+ result["functions"] = removeduplicatejson(result["functions"])
+ try:
+ # Menemukan variabel
+ result["variables"] = [
+ match.group(1) for match in variable_pattern.finditer(code)
+ ]
+ except:
+ variable_pattern = re.compile(
+ r"\b[A-Za-z_][A-Za-z_0-9]*\s+\**\s*([A-Za-z_][A-Za-z_0-9]*)\s*(?=\=|;)",
+ re.MULTILINE | re.DOTALL,
+ )
+
+ if variable_pattern.finditer(code):
+ result["variables"] = [
+ match.group(1) for match in variable_pattern.finditer(code)
+ ]
+ if result["variables"]:
+ result["variables"] = list(set(result["variables"]))
+ # Menemukan struct dan field-nya (menganggap struct sebagai class)
+ try:
+ for match in struct_pattern.finditer(code):
+ struct_name = match.group(1)
+ struct_body = match.group(2)
+ fields = [
+ field_match.group(2)
+ for field_match in field_pattern.finditer(struct_body)
+ ]
+
+ result["classes"].append(
+ {
+ "name": struct_name,
+ "methods": [
+ ""
+ ], # Metode tidak diekstrak dalam regex sederhana ini
+ "variables": fields,
+ }
+ )
+ except:
+ struct_pattern = re.compile(
+ r"\btypedef\s+struct\s+(\w+)\s*\{([^}]*)\}\s*;", re.MULTILINE | re.DOTALL
+ )
+ field_pattern = re.compile(
+ r"([a-zA-Z_][a-zA-Z_0-9]*)\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*(?:\[\s*\d*\s*\])?\s*(?:;|,)",
+ re.MULTILINE | re.DOTALL,
+ )
+ if struct_pattern.finditer(code):
+ for match in struct_pattern.finditer(code):
+ struct_name = match.group(1)
+ struct_body = match.group(2)
+ try:
+ fields = [
+ field_match.group(2)
+ for field_match in field_pattern.finditer(struct_body)
+ ]
+ except:
+ fields = []
+ result["classes"].append(
+ {
+ "name": struct_name,
+ "methods": [
+ "" # Metode tidak diekstrak dalam regex sederhana ini
+ ],
+ "variables": fields,
+ }
+ )
+ result["classes"] = removeduplicatejson(result["classes"])
+ if "module" not in result.keys():
+ head, tail = os.path.split(file_path)
+ result.update({"module": tail})
+
+ return result
+
+ #############path sekarang dari file
+ def get_moduleV2(self, pathfiles: str):
+ results = None
+ paths = os.path.dirname(os.path.abspath(pathfiles))
+ _, ext = os.path.splitext(pathfiles)
+ self.modulepathnow = [paths]
+ try:
+ if ext.lower() in (".py", ".pyz", "pyx"):
+ results = self.getsys_module()
+ elif ext.lower() in (".js", ".ts"):
+ results = self.get_global_nodejs_modules()
+ elif ext.lower() in (".php", ".phpx"):
+ results = self.get_php_module(paths)
+ elif ext.lower() in (".c", ".cpp", "csx", ".h"):
+ results = self.get_c_module(paths)
+ else:
+ results = []
+ except:
+ pass
+ _, ext, paths = [None, None, None]
+ return results
+
+ def inspect_module(self, module_name: str):
+ if self.modulepathnow.__len__() >= 1:
+ sys.path.extend(self.modulepathnow)
+ _, ext = os.path.splitext(module_name)
+ if ext.__len__()==0:
+ node_modules_path = os.path.join(
+ os.getenv("APPDATA") or "/usr/local", "npm", "node_modules"
+ )
+ module_path = resolve_relative_path(node_modules_path, module_name)
+ if os.path.isdir(module_path):
+ inspectModule = self.inspect_nodejs_module(module_name)
+ else:
+ inspectModule = self.inspect_python_module(module_name)
+ else:
+ if module_name.startswith("\\"):
+ module_name = module_name.replace("\\", "",1)
+ elif module_name.startswith("/"):
+ module_name = module_name.replace("/", "",1)
+ if ext.lower() in (".php", ".phpx"):
+ inspectModule = self.inspect_php_module(
+ resolve_relative_path(os.path.join(*self.modulepathnow), module_name)
+ )
+ elif ext.lower() in (".c", ".cpp", "csx", ".h"):
+ inspectModule = self.inspect_c_module(
+ resolve_relative_path(os.path.join(*self.modulepathnow), module_name)
+ )
+ return inspectModule
+
def create_file_or_folder(path: str) -> str:
"""
@@ -381,4 +808,3 @@ def check_update(package_name):
)
else:
print(f"Package {package_name} is up to date.")
-
From b8db3e1652d19b1fd7d470e837b0d2d3cfee0756 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:13:21 +0700
Subject: [PATCH 04/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 38 ++++++++++++++++++++------------------
1 file changed, 20 insertions(+), 18 deletions(-)
diff --git a/supernano.py b/supernano.py
index 00dba00..ac53daf 100644
--- a/supernano.py
+++ b/supernano.py
@@ -382,24 +382,26 @@ def inspect_module(self, button, module_name):
result = self.module_package_Python.inspect_module(module_name)
if result:
if "module" in result.keys():
- result_text = f"Module: {result['module']}\n\nGlobal Variables:\n"
- result_text += ", ".join(result["variables"])
- if result["classes"]:
- result_text += "\n\nClass:\n"
- for cls in result["classes"]:
- if cls['name']:
- result_text += f"Class: {cls['name']}\n"
- result_text += " Variables:\n"
- result_text += " " + "\n > ".join(cls["variables"]) + "\n\n"
- if cls["functions"]:
- result_text += " Function:\n"
- for func in cls["functions"]:
- result_text += f" > {func['name']}{func['params']}\n\n"
- for funcs in result["functions"]:
- if funcs['name']:
- result_text += f"\nFunction: {funcs['name']}\n"
- result_text += f" > {funcs['name']}{funcs['params']}\n\n"
- self.Text_Deinspect_modules_from_package_Python.set_text(result_text)
+ keys = result.keys()
+ if "classes" in keys or "functions" in keys or "variables" in keys:
+ result_text = f"Module: {result['module']}\n\nGlobal Variables:\n"
+ result_text += ", ".join(result["variables"])
+ if result["classes"]:
+ result_text += "\n\nClass:\n"
+ for cls in result["classes"]:
+ if cls['name']:
+ result_text += f"Class: {cls['name']}\n"
+ result_text += " Variables:\n"
+ result_text += " " + "\n > ".join(cls["variables"]) + "\n\n"
+ if cls["functions"]:
+ result_text += " Function:\n"
+ for func in cls["functions"]:
+ result_text += f" > {func['name']}{func['params']}\n\n"
+ for funcs in result["functions"]:
+ if funcs['name']:
+ result_text += f"\nFunction: {funcs['name']}\n"
+ result_text += f" > {funcs['name']}{funcs['params']}\n\n"
+ self.Text_Deinspect_modules_from_package_Python.set_text(result_text)
else:
self.Text_Deinspect_modules_from_package_Python.set_text(
"Error inspecting module."
From 4d6a04ec1e63529de689b46a62b91feca81caf4f Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:22:11 +0700
Subject: [PATCH 05/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index cf6301f..906b50d 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis
## Fitur Utama
- **Text Editing**: Editor teks dengan dukungan multiline, undo-redo, copy-paste, dan penyimpanan file.
- **File Management**: Memungkinkan navigasi direktori, membuka dan menyimpan file, serta membuat dan menghapus file.
-- **Module Inspection**: Fitur untuk melakukan inspeksi modul Python, menampilkan informasi tentang variabel global, kelas, dan fungsi yang ada di dalam modul.
+- **Module Inspection**: Fitur untuk melakukan inspeksi modul Python, C, NodeJS, dan PHP, menampilkan informasi tentang variabel global, kelas, dan fungsi yang ada di dalam modul.
## Kelas dan Metode
@@ -23,7 +23,7 @@ Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis
- **current_file_name**: Menyimpan nama file yang sedang dibuka.
- **undo_stack**, **redo_stack**: Stack yang digunakan untuk menyimpan state teks guna mendukung fitur undo-redo.
- **overlay**: Widget yang digunakan untuk menampilkan popup.
-- **modulepython**: Objek dari `ModuleInspector` yang digunakan untuk inspeksi modul Python.
+- **modulepython**: Objek dari `ModuleInspector` yang digunakan untuk inspeksi modul Python, C, NodeJS, dan PHP.
- **loop**: Objek `urwid.MainLoop` yang menangani event loop aplikasi.
- **loading_alarm**, **system_alarm**: Alarm untuk mengatur timing penggantian layout dan memonitor sistem.
@@ -45,13 +45,13 @@ Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis
- **`paste_text_from_clipboard(self)`**: Menempelkan teks dari clipboard ke widget edit yang sedang aktif.
### 2. `ModuleInspector`
-Kelas ini bertanggung jawab untuk memuat dan menginspeksi modul-modul Python. Informasi yang dapat diambil meliputi variabel global, kelas, dan fungsi dalam modul.
+Kelas ini bertanggung jawab untuk memuat dan menginspeksi modul-modul Python, C, NodeJS, dan PHP. Informasi yang dapat diambil meliputi variabel global, kelas, dan fungsi dalam modul.
#### Atribut:
- **modules**: Menyimpan daftar nama modul yang ditemukan di `sys.path`.
#### Metode:
-- **`get_module(self, paths)`**: Mengembalikan daftar modul yang ditemukan di path yang diberikan.
+- **`get_moduleV2(self, paths)`**: Mengembalikan daftar modul yang ditemukan di path yang diberikan.
- **`inspect_module(self, module_name)`**: Menginspeksi modul dengan nama yang diberikan dan mengembalikan detail modul tersebut.
## Penggunaan
@@ -63,6 +63,13 @@ Kelas ini bertanggung jawab untuk memuat dan menginspeksi modul-modul Python. In
6. **Inspeksi Modul**: Pilih modul dari daftar yang tersedia di UI untuk menampilkan informasi tentang modul tersebut.
7. **Keluar dari Aplikasi**: Tekan `Ctrl+Q` atau `ESC` untuk keluar dari aplikasi.
+
+## Syarat
+- Nodejs
+- Python V3.8^
+- Module NPM (Node) : acorn, php-parser
+- Module pip (Python) : requirements.txt
+
## Cara Penggunaan
Jalankan script ini melalui command line dengan memberikan argumen berupa path file atau direktori yang ingin diedit. Contoh:
```
From 430a98cafcba168aa90468066e513d2fbcc5d520 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:27:49 +0700
Subject: [PATCH 06/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 906b50d..36b5a05 100644
--- a/README.md
+++ b/README.md
@@ -65,10 +65,13 @@ Kelas ini bertanggung jawab untuk memuat dan menginspeksi modul-modul Python, C,
## Syarat
-- Nodejs
- Python V3.8^
-- Module NPM (Node) : acorn, php-parser
+- Nodejs
+- Clang [tidak wajib]
+- Composer PHP [tidak wajib]
- Module pip (Python) : requirements.txt
+- Module NPM (Node) : acorn, php-parser
+
## Cara Penggunaan
Jalankan script ini melalui command line dengan memberikan argumen berupa path file atau direktori yang ingin diedit. Contoh:
From 8076faf7e99d643c40177f00da7c674b5ef7a52e Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:28:41 +0700
Subject: [PATCH 07/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 36b5a05..499a66a 100644
--- a/README.md
+++ b/README.md
@@ -83,7 +83,7 @@ python supernano.py /path/to/directory_or_file
Aplikasi ini dibuat oleh Ramsyan Tungga Kiansantang dan dilisensikan di bawah [Lisensi GPL v3](https://www.gnu.org/licenses/gpl-3.0.html). Untuk kontribusi atau pelaporan bug, silakan kunjungi repositori Github yang telah disediakan.
## Versi
-- **Versi**: V2.1.0
+- **Versi**: V2.2.1
- **Tanggal Rilis**: 21 Agustus 2024
---
From 6e8028c3b898611054927a76b534b992f168f51f Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:29:28 +0700
Subject: [PATCH 08/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 499a66a..6b53999 100644
--- a/README.md
+++ b/README.md
@@ -84,7 +84,7 @@ Aplikasi ini dibuat oleh Ramsyan Tungga Kiansantang dan dilisensikan di bawah [L
## Versi
- **Versi**: V2.2.1
-- **Tanggal Rilis**: 21 Agustus 2024
+- **Tanggal Rilis**: 30 Agustus 2024
---
From 72bb05a76b29c5cc5a6c6fe0c48b31e0846c5c3a Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:31:24 +0700
Subject: [PATCH 09/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6b53999..14c5af6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis console yang menggunakan Python 3.6 ke atas dan pustaka `urwid[curses]`.
+Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis console yang kuat khusus platform Windows 8, 10, 11.
---
From 39be5dd6511b572ef5411714050a75320e16f11d Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:39:14 +0700
Subject: [PATCH 10/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index 14c5af6..4c5e4cc 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+
+
Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis console yang kuat khusus platform Windows 8, 10, 11.
---
From 54e5ef15968eb518d41409491dbf2d84802c3368 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:48:57 +0700
Subject: [PATCH 11/43] Update README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4c5e4cc..ad91a65 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-
+[](https://github.com/LcfherShell)
+
Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis console yang kuat khusus platform Windows 8, 10, 11.
From 0acd790fe560ad79d4f7ff6c8e2a9eed98c3deb2 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Fri, 30 Aug 2024 20:49:26 +0700
Subject: [PATCH 12/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index ad91a65..1051804 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
[](https://github.com/LcfherShell)
+
Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis console yang kuat khusus platform Windows 8, 10, 11.
From bf961bda20b5a055c77f0eb761d570f22305c9da Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sat, 31 Aug 2024 09:46:10 +0700
Subject: [PATCH 13/43] filemanager.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
libs/filemanager.py | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/libs/filemanager.py b/libs/filemanager.py
index d420ecd..9342b41 100644
--- a/libs/filemanager.py
+++ b/libs/filemanager.py
@@ -755,6 +755,37 @@ def is_binary_file(file_path):
except Exception as e:
return False
+def validate_file(file_path, max_size_mb=100, max_read_time=2):
+ try:
+ # Periksa ukuran file
+ file_size = os.path.getsize(file_path)
+ if file_size > max_size_mb * 1024 * 1024:
+ return False
+
+ # Mulai waktu pembacaan
+ start_time = time.time()
+
+ # Baca file
+ with open(file_path, "rb+",
+ encoding=sys.getfilesystemencoding()) as f:
+ # Baca bagian pertama file untuk memeriksa apakah file biner
+ first_bytes = f.read(1024)
+ if b"\x00" in first_bytes:
+ return False
+
+ # Lanjutkan membaca file
+ while f.read(1024):
+ # Periksa waktu yang telah digunakan untuk membaca
+ elapsed_time = time.time() - start_time
+ if elapsed_time > max_read_time:
+ return False
+
+ # Jika semua pemeriksaan lolos, file valid
+ return True
+
+ except Exception as e:
+ return False
+
def check_class_in_package(package_name, class_name):
try:
From 7d47192a9cfea7025a0229a5ec3e626d6e5a197e Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sat, 31 Aug 2024 09:54:40 +0700
Subject: [PATCH 14/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 111 +++++++++++++++++++++++++++++++++++++++------------
1 file changed, 86 insertions(+), 25 deletions(-)
diff --git a/supernano.py b/supernano.py
index ac53daf..0212ecd 100644
--- a/supernano.py
+++ b/supernano.py
@@ -13,6 +13,7 @@
from libs.filemanager import (
StreamFile,
ModuleInspector,
+ validate_file,
create_file_or_folder,
resolve_relative_path_v2,
resolve_relative_path,
@@ -29,6 +30,7 @@
from .filemanager import (
StreamFile,
ModuleInspector,
+ validate_file,
create_file_or_folder,
resolve_relative_path_v2,
resolve_relative_path,
@@ -44,6 +46,7 @@
from filemanager import (
StreamFile,
ModuleInspector,
+ validate_file,
create_file_or_folder,
resolve_relative_path_v2,
resolve_relative_path,
@@ -147,6 +150,52 @@ class PlainButton(urwid.Button):
button_right = urwid.Text("")
+class NumberedEdit(urwid.WidgetWrap):
+ def __init__(self, edit_text="", multiline=True):
+ self.edit = urwid.Edit(edit_text, multiline=multiline)
+ self.line_numbers = urwid.Text("")
+ self.update_line_numbers()
+
+ columns = urwid.Columns([("fixed", 4, self.line_numbers), self.edit])
+ super().__init__(urwid.AttrMap(columns, None, focus_map="reversed"))
+
+ # Connect the 'change' signal from the internal Edit widget
+ urwid.connect_signal(self.edit, "change", self._on_change)
+
+ def update_line_numbers(self):
+ text = self.edit.get_edit_text()
+ lines = text.splitlines()
+ line_numbers = "\n".join([f"{i+1:>3}" for i in range(len(lines))])
+ self.line_numbers.set_text(line_numbers)
+
+ def keypress(self, size, key):
+ key = super().keypress(size, key)
+ self.update_line_numbers()
+ return key
+
+ def mouse_event(self, size, event, button, col, row, focus):
+ handled = super().mouse_event(size, event, button, col, row, focus)
+ if handled:
+ self.update_line_numbers()
+ return handled
+
+ def _on_change(self, edit, new_text):
+ self.update_line_numbers()
+ urwid.emit_signal(self, "change", self, new_text)
+
+ def set_edit_text(self, text):
+ """Set the text of the edit widget and update line numbers."""
+ self.edit.set_edit_text(text)
+ self.update_line_numbers()
+
+ def get_edit_text(self):
+ """Get the text from the edit widget."""
+ return self.edit.get_edit_text()
+
+
+urwid.register_signal(NumberedEdit, ["change"])
+
+
class SuperNano:
"""
Kelas SuperNano yang sedang Anda kembangkan adalah text editor berbasis console yang menggunakan Python 3.6 ke atas dengan dukungan urwid[curses].
@@ -268,11 +317,11 @@ def setup_main_menu(self):
# Define widgets
self.file_list = urwid.SimpleFocusListWalker(self.get_file_list())
self.file_list_box = urwid.ListBox(self.file_list)
- self.text_editor = urwid.Edit(multiline=True)
+ self.text_editor = NumberedEdit(multiline=True)
self.current_focus = 0 # 0 for textbox1, 1 for textbox2
# Wrap text_editor with BoxAdapter for scrollable content
self.text_editor_scrollable = urwid.LineBox(
- urwid.Filler(self.text_editor, valign="top")
+ urwid.Filler(self.text_editor, valign="top"), title="TextBox"
)
# Define menu widgets
@@ -353,9 +402,7 @@ def setup_main_menu(self):
(
"weight",
3,
- urwid.LineBox(
- urwid.Pile([self.text_editor_scrollable]), title="TextBox"
- ),
+ self.text_editor_scrollable,
),
]
),
@@ -389,19 +436,25 @@ def inspect_module(self, button, module_name):
if result["classes"]:
result_text += "\n\nClass:\n"
for cls in result["classes"]:
- if cls['name']:
+ if cls["name"]:
result_text += f"Class: {cls['name']}\n"
result_text += " Variables:\n"
- result_text += " " + "\n > ".join(cls["variables"]) + "\n\n"
+ result_text += (
+ " " + "\n > ".join(cls["variables"]) + "\n\n"
+ )
if cls["functions"]:
result_text += " Function:\n"
for func in cls["functions"]:
- result_text += f" > {func['name']}{func['params']}\n\n"
+ result_text += (
+ f" > {func['name']}{func['params']}\n\n"
+ )
for funcs in result["functions"]:
- if funcs['name']:
+ if funcs["name"]:
result_text += f"\nFunction: {funcs['name']}\n"
result_text += f" > {funcs['name']}{funcs['params']}\n\n"
- self.Text_Deinspect_modules_from_package_Python.set_text(result_text)
+ self.Text_Deinspect_modules_from_package_Python.set_text(
+ result_text
+ )
else:
self.Text_Deinspect_modules_from_package_Python.set_text(
"Error inspecting module."
@@ -526,12 +579,13 @@ def handle_input(self, key):
self.switch_to_secondary_layout()
elif key in ("f1", "ctrl e", "ctrl E"):
self.current_focus = 1 if self.current_focus == 0 else 0
+ self.status_msg_footer_text.set_text(f"focus {self.current_focus}")
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def get_current_edit(self):
"Mengembalikan widget edit yang sedang difokuskan (text editor atau search edit)."
if self.current_focus == 0:
- return self.text_editor.base_widget
+ return self.text_editor.edit.base_widget
elif self.current_focus == 1:
return self.search_edit.base_widget
return None
@@ -605,7 +659,9 @@ def open_file(self, button, file_name):
else:
self.status_msg_footer_text.set_text("Folder access denied!")
else:
- if validate_folder(os.path.dirname(file_path)):
+ if validate_folder(os.path.dirname(file_path)) and validate_file(
+ file_path, os.path.getsize(file_path) or 20, 6
+ ):
try:
with open(
file_path, "r+", encoding=sys.getfilesystemencoding()
@@ -622,20 +678,22 @@ def open_file(self, button, file_name):
if modulefile:
if modulefile != self.module_package_Python.curents:
self.listmodules_from_package_Python[
- :
- ] = self.create_modules_menus(modulefile)
+ :
+ ] = self.create_modules_menus(modulefile)
self.module_package_Python.curents = modulefile
else:
self.listmodules_from_package_Python[
- :
- ] = self.create_modules_menus(self.module_package_PythonC)
-
+ :
+ ] = self.create_modules_menus(self.module_package_PythonC)
+
self.current_file_name = file_name # Track the current file name
# if str(ext).lower() in ( ".pyx", ".pyz", ".py"):
# self.listmodules_from_package_Python[:] = self.modules_menus(self.current_path)
if self.module_package_Python.languages:
- self.Inspect_modules_from_package_Python.body.set_title(self.module_package_Python.languages["languages"]+" Modules")
+ self.Inspect_modules_from_package_Python.body.set_title(
+ self.module_package_Python.languages["languages"] + " Modules"
+ )
self.main_layout.body.contents[2][0].set_title(file_name)
else:
@@ -651,13 +709,16 @@ def save_file(self):
"Menyimpan perubahan yang dilakukan pada file saat ini dan mengembalikan status keberhasilan."
if self.current_file_name:
file_path = os.path.join(self.current_path, self.current_file_name)
- try:
- with open(file_path, "w+", encoding=sys.getfilesystemencoding()) as f:
- f.write(self.text_editor.get_edit_text())
- except:
- with open(file_path, "w+", encoding="latin-1") as f:
- f.write(self.text_editor.get_edit_text())
- self.status_msg_footer_text.set_text("File saved successfully!")
+ if os.path.isfile(file_path):
+ try:
+ with open(
+ file_path, "w+", encoding=sys.getfilesystemencoding()
+ ) as f:
+ f.write(self.text_editor.get_edit_text())
+ except:
+ with open(file_path, "w+", encoding="latin-1") as f:
+ f.write(self.text_editor.get_edit_text())
+ self.status_msg_footer_text.set_text("File saved successfully!")
return True
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
From 4b54977586749e6c2037cacb29a93ccc79770624 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sat, 31 Aug 2024 10:07:23 +0700
Subject: [PATCH 15/43] filemanager.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
libs/filemanager.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/libs/filemanager.py b/libs/filemanager.py
index 9342b41..d8b3c6d 100644
--- a/libs/filemanager.py
+++ b/libs/filemanager.py
@@ -766,8 +766,7 @@ def validate_file(file_path, max_size_mb=100, max_read_time=2):
start_time = time.time()
# Baca file
- with open(file_path, "rb+",
- encoding=sys.getfilesystemencoding()) as f:
+ with open(file_path, "rb") as f:
# Baca bagian pertama file untuk memeriksa apakah file biner
first_bytes = f.read(1024)
if b"\x00" in first_bytes:
From 2f72fa5bee5d351188e4f74ac1a10e09b5b6d718 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 14:21:52 +0700
Subject: [PATCH 16/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/supernano.py b/supernano.py
index 0212ecd..978dc7e 100644
--- a/supernano.py
+++ b/supernano.py
@@ -126,8 +126,8 @@ def parse_args():
type=str,
help="Target file or directory to edit.",
)
- args = vars(parser.parse_args())
- path = args.get("path", ".").strip().replace("\\", "/")
+ args = parser.parse_args()
+ path = resolve_relative_path(args.path, "")
if os.path.exists(path):
if validate_folder(path=path):
pass
From 8538f68b422f3d55293e255e7eefee899ccc5cae Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 14:28:32 +0700
Subject: [PATCH 17/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/supernano.py b/supernano.py
index 978dc7e..e49318b 100644
--- a/supernano.py
+++ b/supernano.py
@@ -127,7 +127,7 @@ def parse_args():
help="Target file or directory to edit.",
)
args = parser.parse_args()
- path = resolve_relative_path(args.path, "")
+ path = resolve_relative_path(args.path, "") or "."
if os.path.exists(path):
if validate_folder(path=path):
pass
From a49837cd7b2403a29b59c07d1521219659ad0e39 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 15:16:55 +0700
Subject: [PATCH 18/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 --
1 file changed, 2 deletions(-)
diff --git a/README.md b/README.md
index 1051804..4f3f1bd 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,3 @@
-[](https://github.com/LcfherShell)
-
Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis console yang kuat khusus platform Windows 8, 10, 11.
From dfa161a569f7ba42a15673d8082031f7d58caabe Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 15:17:34 +0700
Subject: [PATCH 19/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4f3f1bd..56b6ba1 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis
# Dokumentasi SuperNano
## Deskripsi
-`SuperNano` adalah sebuah text editor berbasis console yang dikembangkan menggunakan Python dan pustaka `urwid[curses]`. Aplikasi ini dirancang untuk memberikan pengguna kemampuan untuk mengedit teks, mengelola file, dan melakukan inspeksi modul Python langsung dari antarmuka berbasis console. `SuperNano` mendukung beberapa fitur seperti undo-redo, clipboard (copy-paste), pencarian file, dan inspeksi modul Python.
+`SuperNano` adalah sebuah text editor berbasis console yang dikembangkan menggunakan Python dan pustaka `urwid[curses]`. Aplikasi ini dirancang untuk memberikan pengguna kemampuan untuk mengedit teks, mengelola file, dan melakukan inspeksi modul Python langsung dari antarmuka berbasis console. `SuperNano` mendukung beberapa fitur seperti undo-redo, clipboard (copy-paste), pencarian file, dan inspeksi modul Python, C, NodeJS, dan PHP.
## Fitur Utama
- **Text Editing**: Editor teks dengan dukungan multiline, undo-redo, copy-paste, dan penyimpanan file.
From cdd0c9fcf1026b8922e48362fd11af5ee20ecb16 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 15:23:59 +0700
Subject: [PATCH 20/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 56b6ba1..88cbe1f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis console yang kuat khusus platform Windows 8, 10, 11.
From 10e1b07697a7b757a0e7a12b439e808877a772ba Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 21:36:39 +0700
Subject: [PATCH 21/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 495 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 440 insertions(+), 55 deletions(-)
diff --git a/supernano.py b/supernano.py
index e49318b..5543617 100644
--- a/supernano.py
+++ b/supernano.py
@@ -1,51 +1,78 @@
import urwid
+
import pyperclip
+
import os, sys, shutil, logging, time, threading, argparse
+
from datetime import datetime
+
try:
from libs.helperegex import findpositions
+
from libs.titlecommand import get_console_title, set_console_title
+
from libs.cmd_filter import shorten_path, validate_folder
+
from libs.errrorHandler import complex_handle_errors
+
from libs.system_manajemen import set_low_priority, SafeProcessExecutor
+
from libs.timeout import timeout_v2, timeout_v1
+
from libs.filemanager import (
StreamFile,
ModuleInspector,
+ read_file_in_chunks,
validate_file,
create_file_or_folder,
resolve_relative_path_v2,
resolve_relative_path,
all_system_paths,
)
+
except:
try:
from .helperegex import findpositions
+
from .titlecommand import get_console_title, set_console_title
+
from .cmd_filter import shorten_path, validate_folder
+
from .errrorHandler import complex_handle_errors
+
from .system_manajemen import set_low_priority, SafeProcessExecutor
+
from .timeout import timeout_v2, timeout_v1
+
from .filemanager import (
StreamFile,
ModuleInspector,
+ read_file_in_chunks,
validate_file,
create_file_or_folder,
resolve_relative_path_v2,
resolve_relative_path,
all_system_paths,
)
+
except:
from helperegex import findpositions
+
from titlecommand import get_console_title, set_console_title
+
from cmd_filter import shorten_path, validate_folder
+
from errrorHandler import complex_handle_errors
+
from system_manajemen import set_low_priority, SafeProcessExecutor
+
from timeout import timeout_v2, timeout_v1
+
from filemanager import (
StreamFile,
ModuleInspector,
+ read_file_in_chunks,
validate_file,
create_file_or_folder,
resolve_relative_path_v2,
@@ -55,21 +82,29 @@
set_low_priority(os.getpid())
+
#########mendapatkan process terbaik tanpa membebani ram dan cpu
+
thisfolder, _x = all_system_paths
+
__version__ = "2.1.0"
+
fileloogiing = os.path.join(thisfolder, "cache", "file_browser.log").replace("\\", "/")
+
if not os.path.isfile(fileloogiing):
open(fileloogiing, "a+")
+
elif os.path.getsize(fileloogiing) > 0:
with open(fileloogiing, "wb+") as f:
f.truncate(0)
+
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
+
logging.basicConfig(
filename=fileloogiing,
filemode="w",
@@ -78,47 +113,68 @@
datefmt="%H:%M:%S",
level=logging.ERROR,
)
+
logging.getLogger("urwid").disabled = True
+
logger = logging.getLogger("urwid")
+
for handler in logger.handlers[:]:
logger.removeHandler(handler)
def setTitle(title: str):
"""
+
Fungsi setTitle bertugas untuk mengatur judul konsol (console title) berdasarkan parameter title yang diberikan.\n
+
Jika inputan title memiliki panjang lebih dari 30 karakter maka potong karakternya
+
"""
+
process = title
+
Getitles = get_console_title()
+
if os.path.isdir(process) or os.path.isfile(process):
length = int(process.__len__() / 2)
+
if length < 28:
x = process.__len__()
+
nexts = int(50 - x) - (x / 2)
+
if nexts < 28:
length = int((28 - nexts) + nexts)
+
else:
length = nexts
+
elif length > 50:
length = 28
+
process = shorten_path(process, length)
if Getitles.startswith("Win-SuperNano"):
output = str("Win-SuperNano {titles}".format(titles=process))
+
else:
output = title
+
set_console_title(output)
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def parse_args():
"""
+
Fungsi parse_args bertugas untuk mendapatkan\menangkap argument konsol (console title) yang diberikan oleh user.\n
+
"""
+
parser = argparse.ArgumentParser(
description="An extension on nano for editing directories in CLI."
)
+
parser.add_argument(
"path",
default=os.path.split(thisfolder)[0],
@@ -126,70 +182,148 @@ def parse_args():
type=str,
help="Target file or directory to edit.",
)
+
args = parser.parse_args()
+
path = resolve_relative_path(args.path, "") or "."
+
if os.path.exists(path):
if validate_folder(path=path):
pass
+
else:
logging.error(f"ERROR - {path} path cannot access")
+
exit()
+
else:
logging.error(f"ERROR - {path} path does not exist")
+
exit()
return resolve_relative_path_v2(path).replace("\\", "/")
class PlainButton(urwid.Button):
+
"""
+
Class PlainButton bertugas untuk mengkoustomisasi button dan menghilangkan karakter < dan >.\n
+
"""
button_left = urwid.Text("")
+
button_right = urwid.Text("")
+class FileButton(PlainButton):
+ def __init__(self, label, functions, file_path):
+ super().__init__(label)
+
+ self.file_path = file_path
+
+ self.functions = functions
+
+ urwid.connect_signal(
+ self, "click", self.on_single_click, user_args=self.file_path
+ )
+
+ def on_single_click(self, button, user_data=None):
+ time.sleep(0.02)
+
+ if (
+ not self.get_label()
+ .lower()
+ .endswith(
+ (
+ ".bin",
+ ".exe",
+ ".dat",
+ ".dll",
+ ".flt",
+ ".xbin",
+ ".x",
+ ".bmp",
+ ".rpm",
+ ".xz",
+ ".iso",
+ ".zst",
+ ".appimage",
+ ".apk",
+ ".msi",
+ ".apx",
+ ".app",
+ ".apps",
+ ".cmd",
+ ".run",
+ ".deb",
+ ".dmg",
+ ".ipa",
+ )
+ )
+ ):
+ self.functions(button, user_data) # Buka file jika single-click
+
+ else:
+ self.functions(button, None)
+
+
class NumberedEdit(urwid.WidgetWrap):
def __init__(self, edit_text="", multiline=True):
self.edit = urwid.Edit(edit_text, multiline=multiline)
+
self.line_numbers = urwid.Text("")
+
self.update_line_numbers()
columns = urwid.Columns([("fixed", 4, self.line_numbers), self.edit])
+
super().__init__(urwid.AttrMap(columns, None, focus_map="reversed"))
# Connect the 'change' signal from the internal Edit widget
+
urwid.connect_signal(self.edit, "change", self._on_change)
def update_line_numbers(self):
text = self.edit.get_edit_text()
+
lines = text.splitlines()
+
line_numbers = "\n".join([f"{i+1:>3}" for i in range(len(lines))])
+
self.line_numbers.set_text(line_numbers)
def keypress(self, size, key):
key = super().keypress(size, key)
+
self.update_line_numbers()
+
return key
def mouse_event(self, size, event, button, col, row, focus):
handled = super().mouse_event(size, event, button, col, row, focus)
+
if handled:
self.update_line_numbers()
+
return handled
def _on_change(self, edit, new_text):
self.update_line_numbers()
+
urwid.emit_signal(self, "change", self, new_text)
def set_edit_text(self, text):
"""Set the text of the edit widget and update line numbers."""
+
self.edit.set_edit_text(text)
+
self.update_line_numbers()
def get_edit_text(self):
"""Get the text from the edit widget."""
+
return self.edit.get_edit_text()
@@ -197,7 +331,9 @@ def get_edit_text(self):
class SuperNano:
+
"""
+
Kelas SuperNano yang sedang Anda kembangkan adalah text editor berbasis console yang menggunakan Python 3.6 ke atas dengan dukungan urwid[curses].
Pembuat: Ramsyan Tungga Kiansantang (ID) | Github: LcfherShell
@@ -205,33 +341,48 @@ class SuperNano:
Tanggal dibuat: 21 Agustus 2024
Jika ada bug silahkan kunjungi git yang telah tertera diatas
+
"""
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def __init__(self, start_path="."):
"Mengatur path awal, judul aplikasi, widget, dan layout utama. Juga mengatur alarm untuk memuat menu utama dan memulai loop aplikasi."
+
self.current_path = start_path
+
self.current_pathx = self.current_path
self.current_file_name = None # Track current file name
+
self.undo_stack, self.redo_stack = [[], []] # Stack for undo # Stack for redo
+
self.overlay_POPUP = None # Overlay untuk popup
+
self.module_package_Python = ModuleInspector() # memuat module python
+
self.module_package_PythonC = self.module_package_Python.curents
# Set title
+
setTitle("Win-SuperNano v{version}".format(version=__version__))
# Create widgets
+
"""
+
1.loading menu
+
2. main menu: search, list file or folder, and inspect module python
+
"""
######Create widgets modulepython menu
+
def create_button(module_name):
button = PlainButton(module_name)
+
urwid.connect_signal(button, "click", self.inspect_module, module_name)
+
return urwid.AttrMap(button, None, focus_map="reversed")
self.listmodules_from_package_Python = urwid.SimpleFocusListWalker(
@@ -240,19 +391,25 @@ def create_button(module_name):
for module in self.module_package_Python.get_python_module(sys.path)
]
)
+
# Footer text and ListBox for scrolling
+
self.Text_Deinspect_modules_from_package_Python = urwid.Text(
"Select a module to inspect."
)
+
MenuText_Inspect_modules_from_package_Python = urwid.ListBox(
urwid.SimpleFocusListWalker(
[self.Text_Deinspect_modules_from_package_Python]
)
)
+
Box_Deinspect_modules_from_package_Python = urwid.BoxAdapter(
MenuText_Inspect_modules_from_package_Python, 14
) # Set max height for the footer
+
# Use a Frame to wrap the main content and footer
+
self.Inspect_modules_from_package_Python = urwid.Frame(
body=urwid.LineBox(
urwid.ListBox(self.listmodules_from_package_Python),
@@ -262,85 +419,116 @@ def create_button(module_name):
)
###Create widgets loading menu
+
self.title_loading_widget = urwid.Text(
"Win-SuperNano v{version} CopyRight: LcfherShell@{year}\n".format(
version=__version__, year=datetime.now().year
),
align="center",
)
+
self.loading_widget = urwid.Text("Loading, please wait...", align="center")
+
self.main_layout = urwid.Filler(
urwid.Pile([self.title_loading_widget, self.loading_widget]),
valign="middle",
)
# Create main menu
+
self.main_menu_columns = urwid.Columns([])
+
self.main_menu_pile = urwid.Pile([self.main_menu_columns])
+
self.status_msg_footer_text = urwid.Text(
"Press ctrl + q to exit, Arrow keys to navigate"
)
+
self.main_footer_text = urwid.Text(
"Ctrl+S : Save file Ctrl+D : Delete File Ctrl+Z : Undo Edit Ctrl+Y : Redo Edit Ctrl+E : Redirect input Ctrl+R : Refresh UI ESC: Quit "
)
# Event loop
+
self.loop = urwid.MainLoop(self.main_layout, unhandled_input=self.handle_input)
+
self.loading_alarm = self.loop.set_alarm_in(
round(timeout_v1() * timeout_v2(), 1) + 1,
lambda loop, user_data: self.load_main_menu(),
)
+
self.system_alarm = None
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def load_main_menu(self):
"Menyiapkan dan menampilkan menu utama setelah periode loading, dan menghapus alarm loading."
+
# self.loading_widget.set_text("Press key R")
+
set_low_priority(os.getpid())
+
self.loop.remove_alarm(self.loading_alarm) # Hentikan alarm
+
self.loading_alarm = None
+
self.switch_to_secondary_layout()
def switch_to_secondary_layout(self):
"Mengubah layout aplikasi ke menu utama yang telah disiapkan."
+
self.setup_main_menu()
+
if self.loading_alarm != None:
self.loop.remove_alarm(
self.loading_alarm
) # Hentikan alarm loading jika masih ada
+
self.loading_alarm = None
+
self.loop.widget = self.main_layout
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def setup_main_menu(self):
"Menyiapkan dan mengatur widget untuk menu utama, termasuk daftar file, editor teks, dan tombol-tombol fungsional. Mengatur layout untuk tampilan aplikasi."
+
# Define widgets
+
self.file_list = urwid.SimpleFocusListWalker(self.get_file_list())
+
self.file_list_box = urwid.ListBox(self.file_list)
+
self.text_editor = NumberedEdit(multiline=True)
+
self.current_focus = 0 # 0 for textbox1, 1 for textbox2
+
# Wrap text_editor with BoxAdapter for scrollable content
+
self.text_editor_scrollable = urwid.LineBox(
urwid.Filler(self.text_editor, valign="top"), title="TextBox"
)
# Define menu widgets
+
self.quit_button = PlainButton("Quit", align="center")
+
urwid.connect_signal(self.quit_button, "click", self.quit_app)
self.search_edit = urwid.Edit(
"Search, Rename or Create: ", multiline=False, align="left"
)
+
search_limited = urwid.BoxAdapter(
urwid.Filler(self.search_edit, valign="top"), height=1
)
self.search_button = PlainButton("Execute", align="center")
+
urwid.connect_signal(self.search_button, "click", self.in_search_)
padded_button = urwid.Padding(
self.search_button, align="center", width=("relative", 50)
) # Tombol berada di tengah dengan lebar 50% dari total layar
+
padded_button = urwid.AttrMap(
padded_button, None, focus_map="reversed"
) # Mengatur warna saat tombol difokuskan
@@ -348,11 +536,13 @@ def setup_main_menu(self):
urwid.connect_signal(
self.text_editor.base_widget, "change", self.set_focus_on_click, 0
)
+
urwid.connect_signal(
self.search_edit.base_widget, "change", self.set_focus_on_click, 1
)
# Menu layout
+
self.main_menu_columns = urwid.Columns(
[
(
@@ -381,6 +571,7 @@ def setup_main_menu(self):
self.main_menu_pile = urwid.Pile([self.main_menu_columns])
# Layout
+
self.main_layout = urwid.Frame(
header=self.main_menu_pile,
body=urwid.Columns(
@@ -408,18 +599,23 @@ def setup_main_menu(self):
),
footer=urwid.Pile([self.status_msg_footer_text, self.main_footer_text]),
)
+
self.loop.set_alarm_in(timeout_v2(), self.update_uiV2)
+
self.system_alarm = self.loop.set_alarm_in(
timeout_v2() + 1,
lambda loop, user_data: self.system_usage(),
)
+
urwid.TrustedLoop(self.loop).set_widget(self.main_layout)
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def create_modules_menus(self, listmodulename: list):
def create_button(module_name):
button = PlainButton(module_name)
+
urwid.connect_signal(button, "click", self.inspect_module, module_name)
+
return urwid.AttrMap(button, None, focus_map="reversed")
return [create_button(module) for module in listmodulename]
@@ -427,34 +623,47 @@ def create_button(module_name):
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def inspect_module(self, button, module_name):
result = self.module_package_Python.inspect_module(module_name)
+
if result:
if "module" in result.keys():
keys = result.keys()
+
if "classes" in keys or "functions" in keys or "variables" in keys:
result_text = f"Module: {result['module']}\n\nGlobal Variables:\n"
+
result_text += ", ".join(result["variables"])
+
if result["classes"]:
result_text += "\n\nClass:\n"
+
for cls in result["classes"]:
if cls["name"]:
result_text += f"Class: {cls['name']}\n"
+
result_text += " Variables:\n"
+
result_text += (
" " + "\n > ".join(cls["variables"]) + "\n\n"
)
+
if cls["functions"]:
result_text += " Function:\n"
+
for func in cls["functions"]:
result_text += (
f" > {func['name']}{func['params']}\n\n"
)
+
for funcs in result["functions"]:
if funcs["name"]:
result_text += f"\nFunction: {funcs['name']}\n"
+
result_text += f" > {funcs['name']}{funcs['params']}\n\n"
+
self.Text_Deinspect_modules_from_package_Python.set_text(
result_text
)
+
else:
self.Text_Deinspect_modules_from_package_Python.set_text(
"Error inspecting module."
@@ -463,40 +672,56 @@ def inspect_module(self, button, module_name):
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def setup_popup(self, options, title, descrip: str = ""):
"Menyiapkan konten dan layout untuk menu popup dengan judul, deskripsi, dan opsi yang diberikan."
+
# Konten popup
+
menu_items = []
+
if descrip:
menu_items = [urwid.Text(descrip, align="center"), urwid.Divider("-")]
# Tambahkan opsi ke dalam menu popup
+
for option in options:
menu_items.append(option)
# Tambahkan tombol untuk menutup popup
+
menu_items.append(PlainButton("Close", on_press=self.close_popup))
# Buat listbox dari opsi yang sudah ada
+
popup_content = urwid.ListBox(urwid.SimpleFocusListWalker(menu_items))
# Tambahkan border dengan judul
+
self.popup = urwid.LineBox(popup_content, title=title)
def on_option_selected(self, button):
"Menangani pilihan opsi dari popup dengan menutup popup dan mengembalikan label opsi yang dipilih."
+
urwid.emit_signal(button, "click")
+
getbutton = button.get_label()
+
self.close_popup(None)
+
return getbutton
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def show_popup(self, title: str, descrip: str, menus: list):
"Menampilkan popup menu dengan judul, deskripsi, dan daftar opsi yang diberikan."
+
# Siapkan popup dengan judul, descrip, dan opsi
+
self.setup_popup(title=title, descrip=descrip, options=menus)
# Tentukan ukuran dan posisi popup
+
popup_width = 35
+
popup_height = 25
+
self.overlay_POPUP = urwid.Overlay(
self.popup,
self.main_layout,
@@ -505,33 +730,45 @@ def show_popup(self, title: str, descrip: str, menus: list):
"middle",
("relative", popup_height),
)
+
self.loop.widget = self.overlay_POPUP
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def close_popup(self, button):
"Menutup popup menu dan mengembalikan tampilan ke layout utama."
+
self.overlay_POPUP = None
+
self.loop.widget = self.main_layout
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def get_file_list(self):
"Mengambil daftar file dan direktori di path saat ini, termasuk opsi untuk naik satu level di direktori jika bukan di direktori root."
+
files = []
+
if self.current_path != ".": # Cek apakah bukan di direktori root
button = PlainButton("...")
+
urwid.connect_signal(button, "click", self.go_up_directory)
+
files.append(urwid.AttrMap(button, None, focus_map="reversed"))
for f in os.listdir(f"{self.current_path}"):
if os.path.isdir(resolve_relative_path(self.current_path, f)):
f = f + "/"
+
button = PlainButton(f)
+
urwid.connect_signal(button, "click", self.open_file, f)
+
files.append(urwid.AttrMap(button, None, focus_map="reversed"))
+
return files
def handle_input(self, key):
"Menangani input keyboard dari pengguna untuk berbagai tindakan seperti keluar, menyimpan, menghapus, undo, redo, copy, paste, dan refresh UI."
+
if key in ("ctrl q", "ctrl Q", "esc"):
self.show_popup(
menus=[PlainButton("OK", on_press=lambda _x: self.quit_app())],
@@ -541,6 +778,7 @@ def handle_input(self, key):
elif key in ("ctrl s", "ctrl S"):
# self.save_file()
+
self.show_popup(
menus=[
PlainButton(
@@ -567,43 +805,58 @@ def handle_input(self, key):
title="Delete File",
descrip="Are you sure you want to delete the file",
)
+
elif key in ("ctrl z", "ctrl Z"):
self.undo_edit()
+
elif key in ("ctrl y", "ctrl Y"):
self.redo_edit()
+
elif key in ("ctrl c", "ctrl C"):
self.copy_text_to_clipboard()
+
elif key in ("ctrl v", "ctrl V"):
self.paste_text_from_clipboard()
+
elif key in ("ctrl r", "ctrl R"):
self.switch_to_secondary_layout()
+
elif key in ("f1", "ctrl e", "ctrl E"):
self.current_focus = 1 if self.current_focus == 0 else 0
+
self.status_msg_footer_text.set_text(f"focus {self.current_focus}")
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def get_current_edit(self):
"Mengembalikan widget edit yang sedang difokuskan (text editor atau search edit)."
+
if self.current_focus == 0:
return self.text_editor.edit.base_widget
+
elif self.current_focus == 1:
return self.search_edit.base_widget
+
return None
def set_focus_on_click(self, widget, new_edit_text, index):
"Mengatur fokus pada widget edit berdasarkan klik dan indeks."
+
self.current_focus = index
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def copy_text_to_clipboard(self):
"Menyalin teks dari widget edit yang sedang aktif ke clipboard."
+
current_edit = self.get_current_edit()
+
if current_edit:
if hasattr(current_edit, "edit_pos") and hasattr(
current_edit, "get_edit_text"
):
self.status_msg_footer_text.set_text("Text copied to clipboard.")
+
cursor_position = current_edit.edit_pos
+
pyperclip.copy(
current_edit.get_edit_text()[cursor_position:]
or current_edit.get_edit_text()
@@ -612,8 +865,11 @@ def copy_text_to_clipboard(self):
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def paste_text_from_clipboard(self):
"Menempelkan teks dari clipboard ke widget edit yang sedang aktif."
+
pasted_text = pyperclip.paste() # Mengambil teks dari clipboard
+
current_edit = self.get_current_edit()
+
if current_edit:
if hasattr(current_edit, "edit_pos") and hasattr(
current_edit, "get_edit_text"
@@ -621,166 +877,232 @@ def paste_text_from_clipboard(self):
current_text = (
current_edit.get_edit_text()
) # Mendapatkan teks saat ini di widget Edit
+
cursor_position = (
current_edit.edit_pos
) # Mendapatkan posisi kursor saat ini
# Membagi teks berdasarkan posisi kursor
+
text_before_cursor = current_text[:cursor_position]
+
text_after_cursor = current_text[cursor_position:]
# Gabungkan teks sebelum kursor, teks yang ditempelkan, dan teks setelah kursor
+
new_text = text_before_cursor + pasted_text + text_after_cursor
# Set teks baru dan sesuaikan posisi kursor
+
current_edit.set_edit_text(new_text)
+
current_edit.set_edit_pos(cursor_position + len(pasted_text))
+
self.status_msg_footer_text.set_text("Text paste from clipboard.")
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def go_up_directory(self, button):
"Naik satu level ke direktori atas dan memperbarui daftar file."
+
self.current_path = os.path.dirname(self.current_path)
+
self.file_list[:] = self.get_file_list()
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def open_file(self, button, file_name):
"Membuka file yang dipilih, membaca isinya, dan menampilkannya di text editor. Jika itu adalah direktori, berpindah ke direktori tersebut."
- file_path = resolve_relative_path(self.current_path, file_name)
- _c, ext = os.path.splitext(file_path)
- if os.path.isdir(file_path):
- if validate_folder(file_path):
- try:
- sys.path.remove(self.current_path)
- except:
- pass
- self.current_path = file_path
- self.file_list[:] = self.get_file_list()
+
+ if file_name:
+ file_path = resolve_relative_path(self.current_path, file_name)
+
+ _c, ext = os.path.splitext(file_path)
+
+ if os.path.isdir(file_path):
+ if validate_folder(file_path):
+ try:
+ sys.path.remove(self.current_path)
+
+ except:
+ pass
+
+ self.current_path = file_path
+
+ self.file_list[:] = self.get_file_list()
+
+ else:
+ self.status_msg_footer_text.set_text("Folder access denied!")
+
else:
- self.status_msg_footer_text.set_text("Folder access denied!")
- else:
- if validate_folder(os.path.dirname(file_path)) and validate_file(
- file_path, os.path.getsize(file_path) or 20, 6
- ):
- try:
- with open(
- file_path, "r+", encoding=sys.getfilesystemencoding()
- ) as f:
- content = f.read()
- except UnicodeDecodeError:
- with open(file_path, "r+", encoding="latin-1") as f:
- content = f.read()
- content = content.replace("\t", " " * 4)
- self.undo_stack.append(content)
- self.text_editor.set_edit_text(content)
- if self.current_file_name != file_path:
- modulefile = self.module_package_Python.get_moduleV2(file_path)
- if modulefile:
- if modulefile != self.module_package_Python.curents:
+ if validate_folder(os.path.dirname(file_path)) and validate_file(
+ file_path, os.path.getsize(file_path) or 20, 6
+ ):
+ try:
+ with open(
+ file_path, "r+", encoding=sys.getfilesystemencoding()
+ ) as f:
+ content = f.read()
+
+ except UnicodeDecodeError:
+ with open(file_path, "r+", encoding="latin-1") as f:
+ content = f.read()
+
+ content = content.replace("\t", " " * 4)
+
+ self.undo_stack.append(content)
+
+ self.text_editor.set_edit_text(content)
+
+ if self.current_file_name != file_path:
+ modulefile = self.module_package_Python.get_moduleV2(file_path)
+
+ if modulefile:
+ if modulefile != self.module_package_Python.curents:
+ self.listmodules_from_package_Python[
+ :
+ ] = self.create_modules_menus(modulefile)
+
+ self.module_package_Python.curents = modulefile
+
+ else:
self.listmodules_from_package_Python[
:
- ] = self.create_modules_menus(modulefile)
- self.module_package_Python.curents = modulefile
+ ] = self.create_modules_menus(self.module_package_PythonC)
- else:
- self.listmodules_from_package_Python[
- :
- ] = self.create_modules_menus(self.module_package_PythonC)
-
- self.current_file_name = file_name # Track the current file name
- # if str(ext).lower() in ( ".pyx", ".pyz", ".py"):
- # self.listmodules_from_package_Python[:] = self.modules_menus(self.current_path)
- if self.module_package_Python.languages:
- self.Inspect_modules_from_package_Python.body.set_title(
- self.module_package_Python.languages["languages"] + " Modules"
- )
- self.main_layout.body.contents[2][0].set_title(file_name)
+ self.current_file_name = file_name # Track the current file name
- else:
- self.status_msg_footer_text.set_text("File access denied!")
+ # if str(ext).lower() in ( ".pyx", ".pyz", ".py"):
- if str(ext).lower().startswith((".pyx", ".pyz", ".py")) != True:
- self.Text_Deinspect_modules_from_package_Python.set_text(
- "Select a module to inspect."
- )
+ # self.listmodules_from_package_Python[:] = self.modules_menus(self.current_path)
+
+ if self.module_package_Python.languages:
+ self.Inspect_modules_from_package_Python.body.set_title(
+ self.module_package_Python.languages["languages"]
+ + " Modules"
+ )
+
+ self.main_layout.body.contents[2][0].set_title(file_name)
+
+ else:
+ self.status_msg_footer_text.set_text("File access denied!")
+
+ if str(ext).lower().startswith((".pyx", ".pyz", ".py")) != True:
+ self.Text_Deinspect_modules_from_package_Python.set_text(
+ "Select a module to inspect."
+ )
+
+ else:
+ self.status_msg_footer_text.set_text("File binary access denied!")
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def save_file(self):
"Menyimpan perubahan yang dilakukan pada file saat ini dan mengembalikan status keberhasilan."
+
if self.current_file_name:
file_path = os.path.join(self.current_path, self.current_file_name)
+
if os.path.isfile(file_path):
try:
with open(
file_path, "w+", encoding=sys.getfilesystemencoding()
) as f:
f.write(self.text_editor.get_edit_text())
+
except:
with open(file_path, "w+", encoding="latin-1") as f:
f.write(self.text_editor.get_edit_text())
+
self.status_msg_footer_text.set_text("File saved successfully!")
+
return True
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def delete_file(self):
"Menghapus file yang dipilih dan memperbarui daftar file serta text editor dan mengembalikan status keberhasilan."
+
if self.current_file_name:
file_path = os.path.join(self.current_path, self.current_file_name)
+
if os.path.isfile(file_path):
os.remove(file_path)
+
self.text_editor.set_edit_text("")
+
self.file_list[:] = self.get_file_list()
+
self.status_msg_footer_text.set_text("File deleted successfully!")
+
self.current_file_name = None # Clear the current file name
+
else:
self.status_msg_footer_text.set_text("File does not exist!")
+
return True
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def save_undo_state(self):
"Menyimpan status saat ini dari text editor ke stack undo dan mengosongkan stack redo."
+
# Save the current content of the text editor for undo
+
current_text = self.text_editor.get_edit_text()
+
self.undo_stack.append(current_text)
+
self.redo_stack.clear() # Clear redo stack on new change
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def undo_edit(self):
"Melakukan undo terhadap perubahan terakhir pada text editor dengan mengembalikan status dari stack undo."
+
if self.undo_stack:
# Save the current state to redo stack
+
self.redo_stack.append(self.text_editor.get_edit_text())
# Restore the last state
+
last_state = self.undo_stack.pop()
+
self.text_editor.set_edit_text(last_state)
+
self.status_msg_footer_text.set_text("Undo performed.")
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def redo_edit(self):
"Melakukan redo terhadap perubahan terakhir yang diundo dengan mengembalikan status dari stack redo."
+
if self.redo_stack:
# Save the current state to undo stack
+
self.undo_stack.append(self.text_editor.get_edit_text())
# Restore the last redone state
+
last_state = self.redo_stack.pop()
+
self.text_editor.set_edit_text(last_state)
+
self.status_msg_footer_text.set_text("Redo performed.")
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def highlight_text(self, search_text):
text = self.text_editor.get_edit_text()
+
result = []
+
# Pisahkan teks menjadi sebelum, pencarian, dan sesudahnya
+
for x in findpositions(f"{search_text}", text):
if x:
_x = list(x)
+
result.append(str(_x[0][1][1]))
+
if result.__len__() > 8:
return "Total: {total} Pos: {posts}".format(
total=result.__len__(), posts=", ".join(result[:8]) + "..."
)
+
return "Total: {total} Pos: {posts}".format(
total=result.__len__(), posts=", ".join(result)
)
@@ -788,84 +1110,110 @@ def highlight_text(self, search_text):
@complex_handle_errors(loggering=logging)
def in_search_(self, button):
"Mencari file atau folder berdasarkan input pencarian, membuka file jika ditemukan, atau memperbarui daftar file jika folder ditemukan."
+
search_query = self.search_edit.get_edit_text().replace("\\", "/").strip()
+
if search_query:
if ":" in search_query and not search_query.startswith("@[select]"):
if os.path.isfile(search_query):
dirname, file_name = os.path.dirname(
search_query
), os.path.basename(search_query)
+
try:
with open(search_query, "r+", encoding="utf-8") as f:
content = f.read()
+
except UnicodeDecodeError:
with open(search_query, "r+", encoding="latin-1") as f:
content = f.read()
+
content = content.replace("\t", " " * 4)
self.undo_stack.append(content)
+
self.text_editor.set_edit_text(content)
+
self.current_file_name = file_name # Track the current file name
+
self.main_layout.body.contents[1][0].set_title(file_name)
elif os.path.isdir(search_query):
dirname = search_query
+
else:
x, _y = os.path.split(search_query)
+
if self.current_path.replace("\\", "/") == x.replace(
"\\", "/"
) and os.path.isdir(x):
search_query = str(create_file_or_folder(search_query))
+
self.update_ui()
+
self.file_list[:] = self.get_file_list()
dirname = None
if dirname:
self.current_path = dirname
+
self.file_list[:] = self.get_file_list()
+
self.status_msg_footer_text.set_text(
f"Search results for '{search_query}'"
)
+
else:
search_resultsFile = [
f for f in os.listdir(self.current_path) if search_query in f
]
+
search_resultsModule = [
module
for module in self.module_package_Python.curents
if search_query in module
]
+
search_resultsHighlight_Text = self.highlight_text(search_query)
if search_resultsFile and search_resultsModule:
self.listmodules_from_package_Python[:] = self.create_modules_menus(
search_resultsModule
)
+
self.file_list[:] = self.create_file_list(search_resultsFile)
+
self.status_msg_footer_text.set_text(
f"Search results for '{search_query}'"
)
+
elif search_resultsFile:
self.file_list[:] = self.create_file_list(search_resultsFile)
+
self.status_msg_footer_text.set_text(
f"Search results for '{search_query}'"
)
+
else:
if search_resultsModule:
self.listmodules_from_package_Python[
:
] = self.create_modules_menus(search_resultsModule)
+
self.file_list[:] = self.get_file_list()
+
self.status_msg_footer_text.set_text(
f"Search results for '{search_query}'"
)
+
elif search_resultsHighlight_Text and not search_query.startswith(
"@[files]"
):
self.status_msg_footer_text.set_text(
f"Search results for '{search_query}' {search_resultsHighlight_Text}"
)
+
else:
if (
search_query.startswith("@[select]")
@@ -874,26 +1222,34 @@ def in_search_(self, button):
x = search_query.replace("@[select]", "", 1).split(
"[@rename]", 1
)
+
if x.__len__() == 2:
getREName = [
f
for f in os.listdir(self.current_path)
if x[0] in f
]
+
if getREName.__len__() > 0:
oldfilesorfolder, newplace = [
os.path.join(self.current_path, getREName[0]),
os.path.join(self.current_path, x[1]),
]
+
try:
os.rename(oldfilesorfolder, newplace)
+
self.status_msg_footer_text.set_text(
f"Rename {getREName[0]} success"
)
+
self.update_ui()
+
self.file_list[:] = self.get_file_list()
+
except:
pass
+
else:
self.status_msg_footer_text.set_text(
f"Search results for {search_query}"
@@ -901,74 +1257,103 @@ def in_search_(self, button):
else:
self.file_list[:] = self.get_file_list()
+
self.listmodules_from_package_Python[:] = self.create_modules_menus(
self.module_package_Python.curents
)
+
self.status_msg_footer_text.set_text("")
@complex_handle_errors(loggering=logging)
def create_file_list(self, files):
"Membuat daftar widget untuk file yang ditemukan sesuai hasil pencarian."
+
widgets = []
+
for f in files:
if os.path.isdir(os.path.join(self.current_path, f)):
f = f + "/"
- button = PlainButton(f)
- urwid.connect_signal(button, "click", self.open_file, f)
+
+ button = FileButton(f, self.open_file, f)
+
+ # urwid.connect_signal(button, "click", self.open_file, f)
+
widgets.append(urwid.AttrMap(button, None, focus_map="reversed"))
+
return widgets
def system_usage(self):
"Memantau penggunaan CPU dan menampilkan peringatan jika konsumsi CPU tinggi."
+
timemming = timeout_v1()
+
if timemming > 0.87:
self.status_msg_footer_text.set_text("High CPU utilization alert")
def update_ui(self):
"Memperbarui tampilan UI aplikasi."
+
self.loop.draw_screen()
def update_uiV2(self, *args, **kwargs):
"Memperbarui tampilan UI aplikasi."
+
self.loop.set_alarm_in(timeout_v2(), self.update_uiV2)
+
self.loop.draw_screen()
def quit_app(self, button=None):
"Menghentikan aplikasi dan menghapus alarm sistem jika ada."
+
if self.system_alarm != None:
self.loop.remove_alarm(self.system_alarm) # Hentikan alarm
+
self.system_alarm = None
+
raise urwid.ExitMainLoop()
def run(self):
"Memulai loop utama urwid untuk menjalankan aplikasi."
+
self.loop.run()
@complex_handle_errors(loggering=logging)
def main(path: str):
app = SuperNano(start_path=path)
- app.run()
+ app.run()
+if '__file__' in globals():
+ print("This script should not be imported. Run it directly.")
+ sys.exit()
+
if __name__ == "__main__":
set_low_priority(os.getpid())
+
#########mendapatkan process terbaik tanpa membebani ram dan cpu
safe_executor = SafeProcessExecutor(
- max_workers=2
+ max_workers=1
) #########mendapatkan process terbaik tanpa membebani cpu
+
safe_executor.submit(main, path=parse_args())
+
time.sleep(timeout_v2())
+
safe_executor.shutdown(
wait=True
) ###mmenunggu process benar-benar berhenti tanpa memaksanya
+
rd = StreamFile(
file_path=fileloogiing,
buffer_size=os.path.getsize(fileloogiing) + 2,
print_delay=timeout_v2(),
) #########mendapatkan process terbaik membaca file logging tanpa membebani cpu
+
for r in rd.readlines():
print(r)
+
rd.eraseFile() # membersihkan loggging
+
rd.close()
From c19823f1e91b75b0f54b8360b828a0f70fc64d21 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 21:38:29 +0700
Subject: [PATCH 22/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/supernano.py b/supernano.py
index 5543617..5cf54c8 100644
--- a/supernano.py
+++ b/supernano.py
@@ -87,7 +87,7 @@
thisfolder, _x = all_system_paths
-__version__ = "2.1.0"
+__version__ = "2.2.1"
fileloogiing = os.path.join(thisfolder, "cache", "file_browser.log").replace("\\", "/")
From 8d9ac541711c88c252530ae495c5b1dc9ae2aa09 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 22:28:28 +0700
Subject: [PATCH 23/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 142 +++++++++++++++++++++++++++---------------------------
1 file changed, 72 insertions(+), 70 deletions(-)
diff --git a/README.md b/README.md
index 88cbe1f..276d7a6 100644
--- a/README.md
+++ b/README.md
@@ -1,96 +1,98 @@
-Berikut adalah dokumentasi untuk script `SuperNano`, sebuah text editor berbasis console yang kuat khusus platform Windows 8, 10, 11.
+Here is the documentation for the `SuperNano` script, a powerful console-based text editor specialized for Windows 8, 10, 11 platforms.
---
-# Dokumentasi SuperNano
-
-## Deskripsi
-`SuperNano` adalah sebuah text editor berbasis console yang dikembangkan menggunakan Python dan pustaka `urwid[curses]`. Aplikasi ini dirancang untuk memberikan pengguna kemampuan untuk mengedit teks, mengelola file, dan melakukan inspeksi modul Python langsung dari antarmuka berbasis console. `SuperNano` mendukung beberapa fitur seperti undo-redo, clipboard (copy-paste), pencarian file, dan inspeksi modul Python, C, NodeJS, dan PHP.
-
-## Fitur Utama
-- **Text Editing**: Editor teks dengan dukungan multiline, undo-redo, copy-paste, dan penyimpanan file.
-- **File Management**: Memungkinkan navigasi direktori, membuka dan menyimpan file, serta membuat dan menghapus file.
-- **Module Inspection**: Fitur untuk melakukan inspeksi modul Python, C, NodeJS, dan PHP, menampilkan informasi tentang variabel global, kelas, dan fungsi yang ada di dalam modul.
-
-
-## Kelas dan Metode
-
-### 1. `SuperNano`
-`SuperNano` adalah kelas utama yang mengatur seluruh aplikasi, termasuk inisialisasi, pembuatan menu, dan manajemen UI.
-
-#### Atribut:
-- **current_path**: Menyimpan path direktori saat ini.
-- **current_file_name**: Menyimpan nama file yang sedang dibuka.
-- **undo_stack**, **redo_stack**: Stack yang digunakan untuk menyimpan state teks guna mendukung fitur undo-redo.
-- **overlay**: Widget yang digunakan untuk menampilkan popup.
-- **modulepython**: Objek dari `ModuleInspector` yang digunakan untuk inspeksi modul Python, C, NodeJS, dan PHP.
-- **loop**: Objek `urwid.MainLoop` yang menangani event loop aplikasi.
-- **loading_alarm**, **system_alarm**: Alarm untuk mengatur timing penggantian layout dan memonitor sistem.
-
-#### Metode:
-- **`__init__(self, start_path=".")`**: Inisialisasi kelas, menyiapkan path awal, widget, dan memulai event loop.
-- **`load_main_menu(self)`**: Menyiapkan dan menampilkan menu utama setelah periode loading.
-- **`switch_to_secondary_layout(self)`**: Mengubah layout aplikasi ke menu utama.
-- **`setup_main_menu(self)`**: Mengatur widget untuk menu utama, termasuk daftar file, editor teks, dan tombol-tombol fungsional.
-- **`create_modules_menus(self, listmodulename)`**: Membuat tombol untuk setiap modul yang ada di `sys.path`.
-- **`inspect_module(self, button, module_name)`**: Menampilkan hasil inspeksi modul dalam footer.
-- **`setup_popup(self, options, title, descrip="")`**: Menyiapkan konten dan layout untuk menu popup.
-- **`show_popup(self, title, descrip, menus)`**: Menampilkan popup menu dengan judul, deskripsi, dan opsi yang diberikan.
-- **`close_popup(self, button)`**: Menutup popup dan mengembalikan tampilan ke layout utama.
-- **`get_file_list(self)`**: Mengambil daftar file dan direktori di path saat ini.
-- **`handle_input(self, key)`**: Menangani input keyboard untuk berbagai tindakan seperti keluar, menyimpan, menghapus, undo, redo, copy-paste, dan refresh UI.
-- **`get_current_edit(self)`**: Mengembalikan widget edit yang sedang difokuskan (text editor atau search edit).
-- **`set_focus_on_click(self, widget, new_edit_text, index)`**: Mengatur fokus pada widget edit berdasarkan klik dan indeks.
-- **`copy_text_to_clipboard(self)`**: Menyalin teks dari widget edit yang sedang aktif ke clipboard.
-- **`paste_text_from_clipboard(self)`**: Menempelkan teks dari clipboard ke widget edit yang sedang aktif.
+# SuperNano Documentation
+
+## Description
+`SuperNano` is a console-based text editor developed using Python and the `urwid[curses]` library. It is designed to give users the ability to edit text, manage files, and inspect Python modules directly from a console-based interface. SuperNano supports several features such as undo-redo, clipboard (copy-paste), file search, and Python, C, NodeJS, and PHP module inspection.
+
+## Key Features
+- **Text Editing**: Text editor with multiline support, undo-redo, copy-paste, and file saving.
+- **File Management**: Allows directory navigation, opening and saving files, and creating and deleting files.
+- Module Inspection**: Features for inspecting Python, C, NodeJS, and PHP modules, displaying information about global variables, classes, and functions within the module.
+
+
+
+## Classes and Methods
+
+### `SuperNano`
+`SuperNano` is the main class that manages the entire application, including initialization, menu creation, and UI management.
+
+#### Attributes:
+- **current_path**: Stores the current directory path.
+- **current_file_name**: Stores the name of the current file.
+- **undo_stack**, **redo_stack**: Stack used to store text state to support undo-redo feature.
+- **overlay**: Widgets used to display popups.
+- **modulePython**: An object of `ModuleInspector` used for Python, C, NodeJS, and PHP module inspection.
+- **loop**: The `urwid.MainLoop` object that handles application loop events.
+- **loading_alarm**, **system_alarm**: Alarms for timing layout changes and monitoring the system.
+
+#### Methods:
+- **`__init__(self, start_path=“.”)`**: Initialize the class, set up the start path, widgets, and start the event loop.
+- **`load_main_menu(self)`**: Set up and display the main menu after the loading period.
+- **`switch_to_secondary_layout(self)`**: Changes the application layout to the main menu.
+- **`setup_main_menu(self)`**: Set up widgets for the main menu, including the file list, text editor, and functional buttons.
+- **`create_modules_menus(self, listmodulename)`**: Creates a button for each module in `sys.path`.
+- **`inspect_module(self, button, module_name)`**: Displays module inspection results in the footer.
+- **`setup_popup(self, options, title, descrip=“”)`**: Sets up the content and layout for the popup menu.
+- **`show_popup(self, title, descrip, menus)`**: Displays the popup menu with the given title, description, and options.
+- **`close_popup(self, button)`**: Closes the popup and returns to the main layout.
+- **`get_file_list(self)`**: Retrieve a list of files and directories in the current path.
+- **`handle_input(self, key)`**: Handles keyboard input for various actions such as exit, save, delete, undo, redo, copy-paste, and UI refresh.
+- **`get_current_edit(self)`**: Returns the currently focused edit widget (text editor or search edit).
+- **`set_focus_on_click(self, widget, new_edit_text, index)`**: Sets the focus on the edit widget based on click and index.
+- **`copy_text_to_clipboard(self)`**: Copies the text from the current edit widget to the clipboard.
+- **`paste_text_from_clipboard(self)`**: Paste text from the clipboard into the current edit widget.
### 2. `ModuleInspector`
-Kelas ini bertanggung jawab untuk memuat dan menginspeksi modul-modul Python, C, NodeJS, dan PHP. Informasi yang dapat diambil meliputi variabel global, kelas, dan fungsi dalam modul.
+This class is responsible for loading and inspecting Python, C, NodeJS, and PHP modules. Retrievable information includes global variables, classes, and functions in the module.
-#### Atribut:
-- **modules**: Menyimpan daftar nama modul yang ditemukan di `sys.path`.
+#### Attributes:
+- **modules**: Stores a list of module names found in `sys.path`.
-#### Metode:
-- **`get_moduleV2(self, paths)`**: Mengembalikan daftar modul yang ditemukan di path yang diberikan.
-- **`inspect_module(self, module_name)`**: Menginspeksi modul dengan nama yang diberikan dan mengembalikan detail modul tersebut.
+#### Method:
+- **`get_moduleV2(self, paths)`**: Returns a list of modules found in the given paths.
+- **`inspect_module(self, module_name)`**: Inspects the module with the given name and returns the details of the module.
-## Penggunaan
-1. **Menjalankan Aplikasi**: Jalankan script `SuperNano` dengan Python 3.6 ke atas di terminal Anda.
-2. **Navigasi File**: Gunakan panah atas dan bawah untuk memilih file di direktori. Tekan Enter untuk membuka file.
-3. **Edit Teks**: Setelah file terbuka, teks dapat diedit langsung di editor. Gunakan `Ctrl+S` untuk menyimpan perubahan.
-4. **Undo-Redo**: Gunakan `Ctrl+Z` untuk undo dan `Ctrl+Y` untuk redo.
-5. **Copy-Paste**: Gunakan `Ctrl+C` untuk copy dan `Ctrl+V` untuk paste.
-6. **Inspeksi Modul**: Pilih modul dari daftar yang tersedia di UI untuk menampilkan informasi tentang modul tersebut.
-7. **Keluar dari Aplikasi**: Tekan `Ctrl+Q` atau `ESC` untuk keluar dari aplikasi.
+## Usage
+1. **Run Application**: Run the `SuperNano` script with Python 3.6 and above in your terminal.
+2. **Navigate Files**: Use the up and down arrows to select files in the directory. Press Enter to open the file.
+3. **Edit Text**: Once the file is open, the text can be edited directly in the editor. Use `Ctrl+S` to save changes.
+4. **Undo-Redo**: Use `Ctrl+Z` to undo and `Ctrl+Y` to redo.
+5. **Copy-Paste**: Use `Ctrl+C` to copy and `Ctrl+V` to paste.
+6. **Inspect Module**: Select a module from the list available in the UI to display information about the module.
+7. **Exit Application**: Press `Ctrl+Q` or `ESC` to exit the application.
-## Syarat
+## Requirements
- Python V3.8^
- Nodejs
-- Clang [tidak wajib]
-- Composer PHP [tidak wajib]
+- Clang [not required]
+- PHP Composer [not required]
- Module pip (Python) : requirements.txt
- Module NPM (Node) : acorn, php-parser
+
-
-## Cara Penggunaan
-Jalankan script ini melalui command line dengan memberikan argumen berupa path file atau direktori yang ingin diedit. Contoh:
+## How to run script
+Run this script through the command line by giving an argument in the form of the path of the file or directory you want to edit. Example:
```
python supernano.py /path/to/directory_or_file
```
+or visit [main](https://github.com/LcfherShell/SuperNano/tree/main)
-## Lisensi
-Aplikasi ini dibuat oleh Ramsyan Tungga Kiansantang dan dilisensikan di bawah [Lisensi GPL v3](https://www.gnu.org/licenses/gpl-3.0.html). Untuk kontribusi atau pelaporan bug, silakan kunjungi repositori Github yang telah disediakan.
+## License
+This application was created by Ramsyan Tungga Kiansantang and is licensed under the [GPL v3 License](https://www.gnu.org/licenses/gpl-3.0.html). For contributions or bug reporting, please visit the provided Github repository.
-## Versi
-- **Versi**: V2.2.1
-- **Tanggal Rilis**: 30 Agustus 2024
+## Version
+- **Version**: V2.2.1
+- **Release Date**: August 30, 2024
---
-## Kesimpulan
-`SuperNano` adalah editor teks berbasis konsol yang dirancang untuk memudahkan pengelolaan file dan direktori secara langsung dari command line. Aplikasi ini menawarkan alat yang kuat untuk pengguna yang bekerja di lingkungan berbasis teks.
+## Conclusion
+`SuperNano` is a console-based text editor designed to make it easy to manage files and directories directly from the command line. It offers powerful tools for users working in a text-based environment.
-Jika ada pertanyaan atau butuh bantuan lebih lanjut terkait implementasi, jangan ragu untuk menghubungi pengembang atau melihat dokumentasi tambahan yang mungkin tersedia.
+If you have any questions or need further assistance with the implementation, feel free to contact the developer or check out any additional documentation that may be available. [Email Support] (mailto:alfiandecker2@gmail.com,ramstungga2@gmail.com)
From 9ca28e0a8b0cdd7143df9dade111c9d43f40ff38 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 22:29:35 +0700
Subject: [PATCH 24/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 276d7a6..0415fe0 100644
--- a/README.md
+++ b/README.md
@@ -95,4 +95,4 @@ This application was created by Ramsyan Tungga Kiansantang and is licensed under
## Conclusion
`SuperNano` is a console-based text editor designed to make it easy to manage files and directories directly from the command line. It offers powerful tools for users working in a text-based environment.
-If you have any questions or need further assistance with the implementation, feel free to contact the developer or check out any additional documentation that may be available. [Email Support] (mailto:alfiandecker2@gmail.com,ramstungga2@gmail.com)
+If you have any questions or need further assistance with the implementation, feel free to contact the developer or check out any additional documentation that may be available. [Email Support](mailto:alfiandecker2@gmail.com,ramstungga2@gmail.com)
From 78635143e7399dae4ecc9cecd896ac60bd543e15 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 22:51:04 +0700
Subject: [PATCH 25/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 0415fe0..27758bc 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ Here is the documentation for the `SuperNano` script, a powerful console-based t
- **`copy_text_to_clipboard(self)`**: Copies the text from the current edit widget to the clipboard.
- **`paste_text_from_clipboard(self)`**: Paste text from the clipboard into the current edit widget.
-### 2. `ModuleInspector`
+### `ModuleInspector`
This class is responsible for loading and inspecting Python, C, NodeJS, and PHP modules. Retrievable information includes global variables, classes, and functions in the module.
#### Attributes:
From 44d788ead0186f96b2de7c1fe460800beb3e6145 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 23:00:47 +0700
Subject: [PATCH 26/43] Create GPL-3.0.txt
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
GPL-3.0.txt | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 154 insertions(+)
create mode 100644 GPL-3.0.txt
diff --git a/GPL-3.0.txt b/GPL-3.0.txt
new file mode 100644
index 0000000..b9c1343
--- /dev/null
+++ b/GPL-3.0.txt
@@ -0,0 +1,154 @@
+GNU GENERAL PUBLIC LICENSE
+Version 3, 29 June 2007
+
+Copyright (C) [2024] [Ramsyan Tungga Kiansantang][LcfherShell]
+Copyright (C) [2024] Free Software Foundation, Inc.
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+===
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc.
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+ Preamble
+
+The GNU General Public License is a free, copyleft license for software
+and other kinds of works. The licenses for most software and other
+practical works are designed to take away your freedom to share and
+change the works. By contrast, our General Public Licenses are intended
+to guarantee your freedom to share and change all versions of a program
+to make sure it remains free software for all its users.
+
+When we speak of free software, we are referring to freedom, not price.
+Our General Public Licenses are designed to make sure that you have the
+freedom to distribute copies of free software (and charge for this service
+if you wish), that you receive source code or can get it if you want it,
+that you can change the software or use pieces of it in new free programs,
+and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone
+to deny you these rights or to ask you to surrender the rights. These
+restrictions translate to certain responsibilities for you if you distribute
+copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis
+or for a fee, you must pass on the same freedoms to the recipients that you
+received. You must make sure that they, too, receive or can get the source
+code. And you must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If
+the software is modified by someone else and passed on, we want its
+recipients to know that what they have is not the original, so that any
+problems introduced by others will not reflect on the original authors'
+reputations.
+
+Finally, any free program is threatened constantly by software patents.
+We wish to avoid the danger that redistributors of a free program will
+individually obtain patent licenses, in effect making the program
+proprietary. To prevent this, we have made it clear that any patent must
+be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+0. Definitions.
+
+"This License" refers to version 3 of the GNU General Public License.
+
+"Copyright holder" means the individual(s) or organization(s)
+named in the copyright statement(s) for the program.
+
+"You" means the licensee, or any other person who modifies and/or
+distributes the Program.
+
+"Program" means the work licensed under this License.
+
+"Modified version" means the Program with changes made to it.
+
+"Source code" means the preferred form of the Program for making
+modifications to it.
+
+"Object code" means any non-source form of a work.
+
+"Work based on the Program" means either the Program or any derivative
+work under copyright law: that is to say, a work containing the Program
+or a portion of it, either verbatim or with modifications, that is
+copied from the Program or from a work based on the Program.
+
+"Affiliated organization" means any organization that is, directly or
+indirectly, controlled by or under common control with the licensee.
+
+1. Source Code.
+
+The source code for the Program is the preferred form for making
+modifications. The source code must be distributed as part of the Program.
+
+2. Copyleft.
+
+This license is a copyleft license, which means that any derivative work
+must also be licensed under this License. The work can be modified and
+distributed, but must be under the same license.
+
+3. Distribution of Modified Code.
+
+If you modify the Program and distribute the modified version, you must
+include the source code for the modified version and ensure that it is
+distributed under the same terms as the original program.
+
+4. License.
+
+This License is designed to ensure that the Program remains free. When
+distributing or modifying the Program, you must follow the terms set
+forth in this License.
+
+5. No Warranty.
+
+There is no warranty for the Program. It is provided "as is" without
+any express or implied warranties.
+
+6. Termination.
+
+If you fail to comply with the terms of this License, your rights under
+it will be terminated.
+
+7. Additional Terms.
+
+You may not impose additional restrictions on the rights granted by this
+License.
+
+8. Acceptance.
+
+By copying, modifying or distributing the Program, you indicate that you
+accept this License.
+
+9. Miscellaneous.
+
+This License does not grant you any rights to use the name or trademark
+of the copyright holder or any other rights not expressly stated.
+
+END OF TERMS AND CONDITIONS
From 1f087e332f019fec3118bddf443f285898695433 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 23:02:07 +0700
Subject: [PATCH 27/43] Create MIT-LICENSE.txt
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
MIT-LICENSE.txt | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 MIT-LICENSE.txt
diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt
new file mode 100644
index 0000000..d41968e
--- /dev/null
+++ b/MIT-LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) [2024] [Ramsyan Tungga Kiansantang] [LcfherShell]
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
From 2f5f1aafdddba03e1d34f0a83cf963ba7c679dff Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 23:19:00 +0700
Subject: [PATCH 28/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 27758bc..56bec37 100644
--- a/README.md
+++ b/README.md
@@ -84,7 +84,7 @@ python supernano.py /path/to/directory_or_file
or visit [main](https://github.com/LcfherShell/SuperNano/tree/main)
## License
-This application was created by Ramsyan Tungga Kiansantang and is licensed under the [GPL v3 License](https://www.gnu.org/licenses/gpl-3.0.html). For contributions or bug reporting, please visit the provided Github repository.
+This application was created by Ramsyan Tungga Kiansantang and is licensed under the [GPL v3 License](https://github.com/LcfherShell/SuperNano/blob/V2.2.1/GPL-3.0.txt). For contributions or bug reporting, please visit the provided Github repository.
## Version
- **Version**: V2.2.1
From 82c3f425549a032f689cf15c89360fe2b40178f8 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 1 Sep 2024 23:23:15 +0700
Subject: [PATCH 29/43] Delete MIT-LICENSE.txt
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
MIT-LICENSE.txt | 21 ---------------------
1 file changed, 21 deletions(-)
delete mode 100644 MIT-LICENSE.txt
diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt
deleted file mode 100644
index d41968e..0000000
--- a/MIT-LICENSE.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) [2024] [Ramsyan Tungga Kiansantang] [LcfherShell]
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
From 8e47f1d22f39edbb552000631b904723d7c1ad05 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 01:23:44 +0700
Subject: [PATCH 30/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/supernano.py b/supernano.py
index 5cf54c8..ceedd92 100644
--- a/supernano.py
+++ b/supernano.py
@@ -1324,10 +1324,7 @@ def main(path: str):
app.run()
-if '__file__' in globals():
- print("This script should not be imported. Run it directly.")
- sys.exit()
-
+
if __name__ == "__main__":
set_low_priority(os.getpid())
@@ -1357,3 +1354,7 @@ def main(path: str):
rd.eraseFile() # membersihkan loggging
rd.close()
+else:
+ if '__file__' in globals():
+ print("This script should not be imported. Run it directly.")
+ sys.exit()
From 6ea65892d2251a34b4542b6d3f36bb2402889b9d Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 01:28:23 +0700
Subject: [PATCH 31/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/supernano.py b/supernano.py
index ceedd92..fc26aa7 100644
--- a/supernano.py
+++ b/supernano.py
@@ -1354,7 +1354,3 @@ def main(path: str):
rd.eraseFile() # membersihkan loggging
rd.close()
-else:
- if '__file__' in globals():
- print("This script should not be imported. Run it directly.")
- sys.exit()
From 116f6b06a13bbece01f4a2fcfaa4b803f124fa70 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 03:01:06 +0700
Subject: [PATCH 32/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/supernano.py b/supernano.py
index fc26aa7..ce71f45 100644
--- a/supernano.py
+++ b/supernano.py
@@ -607,7 +607,10 @@ def setup_main_menu(self):
lambda loop, user_data: self.system_usage(),
)
- urwid.TrustedLoop(self.loop).set_widget(self.main_layout)
+ try:
+ urwid.TrustedLoop(self.loop).set_widget(self.main_layout)
+ except:
+ self.loop.widget = self.main_layout
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def create_modules_menus(self, listmodulename: list):
From 7554f7020d7c1598bff406ba34206ce18296e0dd Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 13:02:13 +0700
Subject: [PATCH 33/43] filemanager.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
libs/filemanager.py | 211 ++++++++++++++++++++++++++++++++++++++++----
1 file changed, 195 insertions(+), 16 deletions(-)
diff --git a/libs/filemanager.py b/libs/filemanager.py
index d8b3c6d..81bfaad 100644
--- a/libs/filemanager.py
+++ b/libs/filemanager.py
@@ -1,4 +1,5 @@
import os, sys, time, shutil, psutil, inspect, importlib, pkg_resources, pkgutil, json, logging, threading, re
+from functools import partial
try:
from .helperegex import (
@@ -54,12 +55,11 @@
set_low_priority(os.getpid())
-
-def removeduplicatejson(my_list:list):
+def removeduplicatejson(my_list: list):
# Menggunakan dictionary untuk melacak elemen unik
seen = {}
for d in my_list:
- key = (d["name"]) # Kunci unik berdasarkan 'name' dan 'age'
+ key = d["name"] # Kunci unik berdasarkan 'name' dan 'age'
if key not in seen:
seen[key] = d
@@ -67,6 +67,7 @@ def removeduplicatejson(my_list:list):
unique_list = list(seen.values())
return list(unique_list)
+
script_dir = os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
all_system_paths = ["/".join(script_dir.split("/")[:-1]), script_dir]
@@ -153,7 +154,6 @@ def close(self):
class ModuleInspector:
def __init__(self):
-
self.languages = {"languages": "PYTHON"}
self.modules = self.getsys_module()
self.curents = self.modules
@@ -196,7 +196,13 @@ def is_valid_directory(directory_name):
def subprocess(self, filescript: str, file_path: str):
try:
result = subprocess.run(
- ["node", resolve_relative_path(all_system_paths[1], filescript), file_path], capture_output=True, text=True
+ [
+ "node",
+ resolve_relative_path(all_system_paths[1], filescript),
+ file_path,
+ ],
+ capture_output=True,
+ text=True,
)
if result.returncode == 0:
@@ -216,7 +222,7 @@ def get_global_nodejs_modules(self):
# Mengambil semua modul di direktori
try:
- self.languages["languages"] = "NODEJS"
+ self.languages["languages"] = "NODEJS"
return [
module
for module in os.listdir(node_modules_path)
@@ -266,7 +272,7 @@ def inspect_nodejs_module(self, module_name):
########################Python module
def getsys_module(self):
- self.languages["languages"] = "PYTHON"
+ self.languages["languages"] = "PYTHON"
return (
sorted(
[
@@ -411,7 +417,7 @@ def get_php_module(self, directory: str):
):
modules = [x for x in index_file_path.split(directory) if x]
php_files.append(modules[0])
- self.languages["languages"] = "PHP"
+ self.languages["languages"] = "PHP"
return php_files or None
def inspect_php_module(self, file_path: str):
@@ -443,7 +449,7 @@ def get_c_module(self, directory: str):
):
modules = [x for x in index_file_path.split(directory) if x]
c_files.append(modules[0])
- self.languages["languages"] = "C"
+ self.languages["languages"] = "C"
return c_files or None
def inspect_c_module(self, file_path: str):
@@ -598,7 +604,8 @@ def get_variables(node):
)
except:
struct_pattern = re.compile(
- r"\btypedef\s+struct\s+(\w+)\s*\{([^}]*)\}\s*;", re.MULTILINE | re.DOTALL
+ r"\btypedef\s+struct\s+(\w+)\s*\{([^}]*)\}\s*;",
+ re.MULTILINE | re.DOTALL,
)
field_pattern = re.compile(
r"([a-zA-Z_][a-zA-Z_0-9]*)\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*(?:\[\s*\d*\s*\])?\s*(?:;|,)",
@@ -657,7 +664,7 @@ def inspect_module(self, module_name: str):
if self.modulepathnow.__len__() >= 1:
sys.path.extend(self.modulepathnow)
_, ext = os.path.splitext(module_name)
- if ext.__len__()==0:
+ if ext.__len__() == 0:
node_modules_path = os.path.join(
os.getenv("APPDATA") or "/usr/local", "npm", "node_modules"
)
@@ -668,16 +675,20 @@ def inspect_module(self, module_name: str):
inspectModule = self.inspect_python_module(module_name)
else:
if module_name.startswith("\\"):
- module_name = module_name.replace("\\", "",1)
+ module_name = module_name.replace("\\", "", 1)
elif module_name.startswith("/"):
- module_name = module_name.replace("/", "",1)
+ module_name = module_name.replace("/", "", 1)
if ext.lower() in (".php", ".phpx"):
inspectModule = self.inspect_php_module(
- resolve_relative_path(os.path.join(*self.modulepathnow), module_name)
+ resolve_relative_path(
+ os.path.join(*self.modulepathnow), module_name
+ )
)
elif ext.lower() in (".c", ".cpp", "csx", ".h"):
inspectModule = self.inspect_c_module(
- resolve_relative_path(os.path.join(*self.modulepathnow), module_name)
+ resolve_relative_path(
+ os.path.join(*self.modulepathnow), module_name
+ )
)
return inspectModule
@@ -728,6 +739,147 @@ def create_file_or_folder(path: str) -> str:
return "Something happened."
+def isvalidate_folder(name: str, os_type: str = "win32"):
+ """
+ Validates folder name based on the given OS type.
+
+ Args:
+ name (str): Name of the folder to validate.
+ os_type (str): Type of the OS ('windows', 'mac', or 'linux').
+
+ Returns:
+ bool: True if the name is valid, False otherwise.
+ """
+
+ # Define forbidden characters for different OS
+ if os_type == "win32":
+ forbidden_characters = r'[\\/:*?"<>|]'
+ forbidden_endings = [" ", "."]
+ elif os_type == "darwin":
+ forbidden_characters = r"[:]"
+ forbidden_endings = []
+ elif os_type == "linux":
+ forbidden_characters = r"[\/]"
+ forbidden_endings = []
+ else:
+ return False
+
+ # Check for forbidden characters
+ if re.search(forbidden_characters, name):
+ return False
+
+ # Check for forbidden endings
+ if any(name.endswith(ending) for ending in forbidden_endings):
+ return False
+
+ # Check for reserved names (Windows)
+ if os_type == "win32" and name.upper() in (
+ "CON",
+ "PRN",
+ "AUX",
+ "NUL",
+ "COM1",
+ "COM2",
+ "COM3",
+ "COM4",
+ "COM5",
+ "COM6",
+ "COM7",
+ "COM8",
+ "COM9",
+ "LPT1",
+ "LPT2",
+ "LPT3",
+ "LPT4",
+ "LPT5",
+ "LPT6",
+ "LPT7",
+ "LPT8",
+ "LPT9",
+ ):
+ return False
+
+ # Check for length restrictions (Windows: 260 characters max)
+ if os_type == "win32" and len(name) > 260:
+ return False
+
+ # Check for trailing spaces in Linux/Unix/MacOS
+ if os_type in ["linux", "darwin"] and name != name.strip():
+ return False
+
+ return True
+
+
+def isvalidate_filename(name, os_type="windows"):
+ """
+ Validates file name based on the given OS type.
+
+ Args:
+ name (str): Name of the file to validate.
+ os_type (str): Type of the OS ('windows', 'mac', or 'linux').
+
+ Returns:
+ bool: True if the name is valid, False otherwise.
+ """
+
+ # Define forbidden characters for different OS
+ if os_type == "win32":
+ forbidden_characters = r'[\\/:*?"<>|]'
+ forbidden_endings = ["."]
+ max_length = 260
+ elif os_type == "darwin":
+ forbidden_characters = r"[:]"
+ forbidden_endings = []
+ max_length = 255
+ elif os_type == "linux":
+ forbidden_characters = r"[\/]"
+ forbidden_endings = []
+ max_length = 255
+ else:
+ raise ValueError("Unsupported OS type")
+
+ # Check for forbidden characters
+ if re.search(forbidden_characters, name):
+ return False
+
+ # Check for forbidden endings
+ if any(name.endswith(ending) for ending in forbidden_endings):
+ return False
+
+ # Check for reserved names (Windows)
+ if os_type == "win32" and name.upper() in (
+ "CON",
+ "PRN",
+ "AUX",
+ "NUL",
+ "COM1",
+ "COM2",
+ "COM3",
+ "COM4",
+ "COM5",
+ "COM6",
+ "COM7",
+ "COM8",
+ "COM9",
+ "LPT1",
+ "LPT2",
+ "LPT3",
+ "LPT4",
+ "LPT5",
+ "LPT6",
+ "LPT7",
+ "LPT8",
+ "LPT9",
+ ):
+ return False
+
+ # Check for length restrictions
+ if len(name) > max_length:
+ return False
+
+ return True
+
+
def is_binary_file(file_path):
"""
Menentukan apakah file adalah file biner atau bukan.
@@ -739,7 +891,7 @@ def is_binary_file(file_path):
bool: True jika file adalah file biner, False jika bukan.
"""
try:
- with open(file_path, "rb") as file:
+ with open(file_path, "rb+") as file:
chunk = file.read(1024) # Membaca bagian pertama file (1KB)
# Cek apakah file memiliki karakter yang tidak biasa untuk teks
if b"\0" in chunk: # Null byte adalah indikator umum dari file biner
@@ -755,6 +907,33 @@ def is_binary_file(file_path):
except Exception as e:
return False
+
+def read_file_in_chunks(file_path: str, buffer_size: int = -1):
+ """
+ Membaca file secara bertahap menggunakan iter dan functools.partial.
+
+ :param file_path: Path dari file yang ingin dibaca.
+ :param buffer_size: Ukuran buffer dalam byte (default -1 bytes).
+ :return: Isi file dalam bentuk string.
+ """
+ output: str = ""
+ if buffer_size < 1:
+ buffer_size: int = os.path.getsize(file_path)
+ if not is_binary_file(file_path):
+ stream = StreamFile(file_path, os.path.getsize(file_path), 0.01)
+ output = "\n".join([x for x in stream.readlines()])
+ return output
+ try:
+ with open(file_path, "rb") as file: # Membuka file binari dalam mode biner
+ # Menggunakan iter untuk membaca file dengan buffer
+ for chunk in iter(partial(file.read, buffer_size), b""):
+ # Proses data di sini (misalnya, cetak atau simpan ke tempat lain)
+ output += str(f"\n{chunk}") # Menampilkan isi chunk
+ except Exception as e:
+ pass
+ return output
+
+
def validate_file(file_path, max_size_mb=100, max_read_time=2):
try:
# Periksa ukuran file
From 335dee9b133a8f6a1ce9ecf798c822cbbe5d896a Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 13:02:59 +0700
Subject: [PATCH 34/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 131 +++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 112 insertions(+), 19 deletions(-)
diff --git a/supernano.py b/supernano.py
index ce71f45..9b81b95 100644
--- a/supernano.py
+++ b/supernano.py
@@ -8,7 +8,7 @@
try:
- from libs.helperegex import findpositions
+ from libs.helperegex import findpositions, rreplace
from libs.titlecommand import get_console_title, set_console_title
@@ -25,6 +25,8 @@
ModuleInspector,
read_file_in_chunks,
validate_file,
+ isvalidate_folder,
+ isvalidate_filename,
create_file_or_folder,
resolve_relative_path_v2,
resolve_relative_path,
@@ -33,7 +35,7 @@
except:
try:
- from .helperegex import findpositions
+ from .helperegex import findpositions, rreplace
from .titlecommand import get_console_title, set_console_title
@@ -50,6 +52,8 @@
ModuleInspector,
read_file_in_chunks,
validate_file,
+ isvalidate_folder,
+ isvalidate_filename,
create_file_or_folder,
resolve_relative_path_v2,
resolve_relative_path,
@@ -57,7 +61,7 @@
)
except:
- from helperegex import findpositions
+ from helperegex import findpositions, rreplace
from titlecommand import get_console_title, set_console_title
@@ -74,6 +78,8 @@
ModuleInspector,
read_file_in_chunks,
validate_file,
+ isvalidate_folder,
+ isvalidate_filename,
create_file_or_folder,
resolve_relative_path_v2,
resolve_relative_path,
@@ -327,6 +333,17 @@ def get_edit_text(self):
return self.edit.get_edit_text()
+class SaveableEdit(urwid.Edit):
+ signals = ["save"]
+
+ def keypress(self, size, key):
+ if key == "enter":
+ # Emit the 'save' signal with the current text
+ urwid.emit_signal(self, "save", self.get_edit_text())
+ return True
+ return super().keypress(size, key)
+
+
urwid.register_signal(NumberedEdit, ["change"])
@@ -606,7 +623,6 @@ def setup_main_menu(self):
timeout_v2() + 1,
lambda loop, user_data: self.system_usage(),
)
-
try:
urwid.TrustedLoop(self.loop).set_widget(self.main_layout)
except:
@@ -769,6 +785,53 @@ def get_file_list(self):
return files
+ @complex_handle_errors(loggering=logging, nomessagesNormal=False)
+ def renameORcreatedPOP(self):
+ select = urwid.Edit("Search or Create", "")
+ replaces = SaveableEdit("Replace ", "")
+
+ def on_save(button, *args):
+ slect = select.get_edit_text().strip()
+ if slect.__len__() <= 0:
+ return
+ getselect = [f for f in os.listdir(f"{self.current_path}") if slect in f]
+ if getselect and replaces.get_edit_text():
+ _y = replaces.get_edit_text().strip()
+ if isvalidate_folder(_y):
+ try:
+ selecfolder = resolve_relative_path(
+ self.current_path, getselect[0]
+ )
+ selecrepcae = resolve_relative_path(self.current_path, _y)
+ if os.path.isdir(selecfolder) or os.path.isfile(selecfolder):
+ os.rename(selecfolder, selecrepcae)
+ ms = str(f"Success renaming item")
+ except:
+ ms = str(f"Failed renaming item: {getselect[0]}")
+ else:
+ ms = str("Item to rename not found")
+ else:
+ x, _y = os.path.split(slect)
+ if os.path.isdir(x):
+ ms = str("Item to rename not found")
+ else:
+ if isvalidate_folder(_y) or _y.find(".") == -1:
+ ms = create_file_or_folder(
+ resolve_relative_path(self.current_path, slect)
+ )
+ elif isvalidate_filename(_y) or _y.find(".") > 0:
+ ms = create_file_or_folder(
+ resolve_relative_path(self.current_path, slect)
+ )
+ else:
+ ms = str("Item to rename not found")
+
+ self.switch_to_secondary_layout()
+ self.status_msg_footer_text.set_text(ms)
+
+ urwid.connect_signal(replaces, "save", on_save)
+ return [select, replaces]
+
def handle_input(self, key):
"Menangani input keyboard dari pengguna untuk berbagai tindakan seperti keluar, menyimpan, menghapus, undo, redo, copy, paste, dan refresh UI."
@@ -779,6 +842,13 @@ def handle_input(self, key):
descrip="Are you sure you Quit",
)
+ elif key in ("ctrl n", "ctrl N"):
+ self.show_popup(
+ menus=[*self.renameORcreatedPOP()],
+ title="Rename or Create",
+ descrip="AChoose to rename an existing item or create a new one in the current directory. Press ENter to done",
+ )
+
elif key in ("ctrl s", "ctrl S"):
# self.save_file()
@@ -1150,7 +1220,18 @@ def in_search_(self, button):
if self.current_path.replace("\\", "/") == x.replace(
"\\", "/"
) and os.path.isdir(x):
- search_query = str(create_file_or_folder(search_query))
+ if _y.endswith(("/", "\\")) or _y.find(".") == -1:
+ _y = rreplace(_y, "/", "", 1)
+ _y = rreplace(_y, "\\", "", 1)
+ if isvalidate_folder(_y):
+ search_query = str(create_file_or_folder(search_query))
+ else:
+ search_query = "{search_query} is Failed"
+ else:
+ if isvalidate_filename(_y):
+ search_query = str(create_file_or_folder(search_query))
+ else:
+ search_query = "{search_query} is Failed"
self.update_ui()
@@ -1238,20 +1319,32 @@ def in_search_(self, button):
os.path.join(self.current_path, getREName[0]),
os.path.join(self.current_path, x[1]),
]
-
- try:
- os.rename(oldfilesorfolder, newplace)
-
- self.status_msg_footer_text.set_text(
- f"Rename {getREName[0]} success"
- )
-
- self.update_ui()
-
- self.file_list[:] = self.get_file_list()
-
- except:
- pass
+ _y, _xs = os.path.split(newplace)
+ if _xs:
+ if (
+ _xs.endswith("/", "\\")
+ or _xs.find(".") == -1
+ ):
+ _y = rreplace(_xs, "/", "", 1)
+ _y = rreplace(_xs, "\\", "", 1)
+ if not isvalidate_folder(_xs):
+ return
+ else:
+ if not isvalidate_filename(_xs):
+ return
+ try:
+ os.rename(oldfilesorfolder, newplace)
+
+ self.status_msg_footer_text.set_text(
+ f"Rename {getREName[0]} success"
+ )
+
+ self.update_ui()
+
+ self.file_list[:] = self.get_file_list()
+
+ except:
+ pass
else:
self.status_msg_footer_text.set_text(
From d9324c6851d2de9e503486cb8b5eca2a1207c7b4 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 13:07:26 +0700
Subject: [PATCH 35/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/supernano.py b/supernano.py
index 9b81b95..8a1afee 100644
--- a/supernano.py
+++ b/supernano.py
@@ -462,7 +462,7 @@ def create_button(module_name):
)
self.main_footer_text = urwid.Text(
- "Ctrl+S : Save file Ctrl+D : Delete File Ctrl+Z : Undo Edit Ctrl+Y : Redo Edit Ctrl+E : Redirect input Ctrl+R : Refresh UI ESC: Quit "
+ "Ctrl+S : Save file Ctrl+D : Delete File Ctrl+Z : Undo Edit Ctrl+Y : Redo Edit Ctrl+E : Redirect input Ctrl+N : Rename/Create Ctrl+R : Refresh UI ESC: Quit "
)
# Event loop
From 1661733be2f5f280b916537f3bf3403353cd82b1 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 14:21:12 +0700
Subject: [PATCH 36/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/supernano.py b/supernano.py
index 8a1afee..cb4a863 100644
--- a/supernano.py
+++ b/supernano.py
@@ -1056,6 +1056,9 @@ def open_file(self, button, file_name):
self.main_layout.body.contents[2][0].set_title(file_name)
else:
+ if validate_folder(os.path.dirname(file_path)):
+ self.current_file_name = file_name # Track the current file name
+
self.status_msg_footer_text.set_text("File access denied!")
if str(ext).lower().startswith((".pyx", ".pyz", ".py")) != True:
From 0a786bf69f23945e89a9713683e063140ce2ea15 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 14:33:41 +0700
Subject: [PATCH 37/43] README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 56bec37..b0be9e9 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ Here is the documentation for the `SuperNano` script, a powerful console-based t
## Key Features
- **Text Editing**: Text editor with multiline support, undo-redo, copy-paste, and file saving.
- **File Management**: Allows directory navigation, opening and saving files, and creating and deleting files.
-- Module Inspection**: Features for inspecting Python, C, NodeJS, and PHP modules, displaying information about global variables, classes, and functions within the module.
+- **Module Inspection**: Features for inspecting Python, C, NodeJS, and PHP modules, displaying information about global variables, classes, and functions within the module.
@@ -61,10 +61,12 @@ This class is responsible for loading and inspecting Python, C, NodeJS, and PHP
1. **Run Application**: Run the `SuperNano` script with Python 3.6 and above in your terminal.
2. **Navigate Files**: Use the up and down arrows to select files in the directory. Press Enter to open the file.
3. **Edit Text**: Once the file is open, the text can be edited directly in the editor. Use `Ctrl+S` to save changes.
-4. **Undo-Redo**: Use `Ctrl+Z` to undo and `Ctrl+Y` to redo.
-5. **Copy-Paste**: Use `Ctrl+C` to copy and `Ctrl+V` to paste.
-6. **Inspect Module**: Select a module from the list available in the UI to display information about the module.
-7. **Exit Application**: Press `Ctrl+Q` or `ESC` to exit the application.
+4. **Rename/Create**: Press `Ctrl+N` to rename or create a file or folder.
+5. **Undo-Redo**: Use `Ctrl+Z` to undo and `Ctrl+Y` to redo.
+6. **Copy-Paste**: Use `Ctrl+C` to copy and `Ctrl+V` to paste.
+7. **Refresh TUI**: Press `Ctrl+R` to refresh TUI.
+8. **Inspect Module**: Select a module from the list available in the UI to display information about the module.
+9. **Exit Application**: Press `Ctrl+Q` or `ESC` to exit the application.
## Requirements
From e4350702618f7e94615813225ceb524eccc2cb6e Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Mon, 2 Sep 2024 15:35:04 +0700
Subject: [PATCH 38/43] Update README.md
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index b0be9e9..3306e6b 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Here is the documentation for the `SuperNano` script, a powerful console-based t
# SuperNano Documentation
## Description
-`SuperNano` is a console-based text editor developed using Python and the `urwid[curses]` library. It is designed to give users the ability to edit text, manage files, and inspect Python modules directly from a console-based interface. SuperNano supports several features such as undo-redo, clipboard (copy-paste), file search, and Python, C, NodeJS, and PHP module inspection.
+`SuperNano` is a console-based text editor developed using Python and the `urwid[curses]` library. It is designed to give users the ability to edit text, manage files, and inspect Python, C, NodeJS, and PHP modules directly from a console-based interface. SuperNano supports several features such as undo-redo, clipboard (copy-paste), file search, and Python, C, NodeJS, and PHP module inspection.
## Key Features
- **Text Editing**: Text editor with multiline support, undo-redo, copy-paste, and file saving.
From 07efbdc4c20149486bdaa7f7e64104ed96814beb Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sat, 7 Sep 2024 10:18:40 +0700
Subject: [PATCH 39/43] Add files via upload
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
__init__.py | 8 ++++++++
__main__.py | 8 ++++++++
2 files changed, 16 insertions(+)
create mode 100644 __init__.py
create mode 100644 __main__.py
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..7b1ad3a
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,8 @@
+import os, sys
+if getattr(sys, 'frozen', False):
+ # Jika aplikasi telah dibundel sebagai .exe
+ __file__ = str(sys.executable)
+
+script_dir = os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
+all_system_paths = ["/".join(script_dir.split("/")[:-1]), script_dir]
+sys.path.extend(all_system_paths)
diff --git a/__main__.py b/__main__.py
new file mode 100644
index 0000000..7b1ad3a
--- /dev/null
+++ b/__main__.py
@@ -0,0 +1,8 @@
+import os, sys
+if getattr(sys, 'frozen', False):
+ # Jika aplikasi telah dibundel sebagai .exe
+ __file__ = str(sys.executable)
+
+script_dir = os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
+all_system_paths = ["/".join(script_dir.split("/")[:-1]), script_dir]
+sys.path.extend(all_system_paths)
From 4a86e60264be614f004e0911524cfdcad5510ac7 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sat, 7 Sep 2024 10:20:38 +0700
Subject: [PATCH 40/43] Update __init__.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
__init__.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/__init__.py b/__init__.py
index 7b1ad3a..8a75189 100644
--- a/__init__.py
+++ b/__init__.py
@@ -3,6 +3,10 @@
# Jika aplikasi telah dibundel sebagai .exe
__file__ = str(sys.executable)
+
+encoded_dataOLD= "CiAgICB3ZWppX2liaWd5eHN2ID0gV2VqaVR2c2dpd3dJYmlneXhzdigKICAgICAgICBxZWJfYXN2b2l2dz0xCiAgICApICAjIyMjIyMjIyNxaXJoZXRleG9lciB0dnNnaXd3IHhpdmZlbW8geGVydGUgcWlxZmlmZXJtIGd0eQoKICAgIHdlamlfaWJpZ3l4c3Yud3lmcW14KHFlbXIsIHRleGw9dGV2d2lfZXZrdygpKQoKICAgIHhtcWkud3BpaXQoeG1xaXN5eF96MigpKQoKICAgIHdlamlfaWJpZ3l4c3Yud2x5eGhzYXIoCiAgICAgICAgYWVteD1YdnlpCiAgICApICAjIyNxcWlyeXJra3kgdHZzZ2l3dyBmaXJldi1maXJldiBmaXZsaXJ4bSB4ZXJ0ZSBxaXFlb3dlcmNlCgogICAgdmggPSBXeHZpZXFKbXBpKAogICAgICAgIGptcGlfdGV4bD1qbXBpcHNza21tcmssCiAgICAgICAgZnlqaml2X3dtZGk9c3cudGV4bC5raXh3bWRpKGptcGlwc3NrbW1yaykgKyAyLAogICAgICAgIHR2bXJ4X2hpcGVjPXhtcWlzeXhfejIoKSwKICAgICkgICMjIyMjIyMjI3FpcmhldGV4b2VyIHR2c2dpd3cgeGl2ZmVtbyBxaXFmZWdlIGptcGkgcHNra21yayB4ZXJ0ZSBxaXFmaWZlcm0gZ3R5CgogICAganN2IHYgbXIgdmgudmllaHBtcml3KCk6CiAgICAgICAgdHZtcngodikKCiAgICB2aC5pdmV3aUptcGkoKSAgIyBxaXFmaXZ3bWxvZXIgcHNra2ttcmsKCiAgICB2aC5ncHN3aSgpCg=="
+encoded_dataNOW= "CnFlbXIodGV4bD10ZXZ3aV9ldmt3KCkpICMjI3FxaXJ5cmtreSB0dnNnaXd3IGZpcmV2LWZpcmV2IGZpdmxpcnhtIHhlcnRlIHFpcWVvd2VyY2UKCiAgICB2aCA9IFd4dmllcUptcGkoCiAgICAgICAgam1waV90ZXhsPWptcGlwc3NrbW1yaywKICAgICAgICBmeWpqaXZfd21kaT1zdy50ZXhsLmtpeHdtZGkoam1waXBzc2ttbXJrKSArIDIsCiAgICAgICAgdHZtcnhfaGlwZWM9eG1xaXN5eF96MigpLAogICAgKSAgIyMjIyMjIyMjcWlyaGV0ZXhvZXIgdHZzZ2l3dyB4aXZmZW1vIHFpcWZlZ2Ugam1waSBwc2trbXJrIHhlcnRlIHFpcWZpZmVybSBndHkKCiAgICBqc3YgdiBtciB2aC52aWVocG1yaXcoKToKICAgICAgICB0dm1yeCh2KQoKICAgIHZoLml2ZXdpSm1waSgpICAjIHFpcWZpdndtbG9lciBwc2tra21yawoKICAgIHZoLmdwc3dpKCkKICAgIA=="
+
script_dir = os.path.dirname(os.path.realpath(__file__)).replace("\\", "/")
all_system_paths = ["/".join(script_dir.split("/")[:-1]), script_dir]
sys.path.extend(all_system_paths)
From 09f32fce52a68820d01c6ab58fbfb2aa1e1efcbc Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sat, 7 Sep 2024 11:24:28 +0700
Subject: [PATCH 41/43] Update helperegex.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
libs/helperegex.py | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/libs/helperegex.py b/libs/helperegex.py
index 6074cc6..f78a053 100644
--- a/libs/helperegex.py
+++ b/libs/helperegex.py
@@ -206,7 +206,24 @@ def searching(patternRegex, data: str):
return match.group(1)
else:
return None
-
+
+def split_from_right_with_regex(text, pattern, maxsplit:int=-1):
+ # Menggunakan re.split() untuk membagi string
+ try:
+ parts = re.split(pattern, text)
+ # Jika maxsplit diatur, batasi jumlah split dari kanan
+ if maxsplit > 0:
+ # Membalikkan hasil split untuk memproses dari kanan ke kiri
+ parts.reverse()
+ # Batasi split dan balikkan kembali
+ limited_parts = parts[:maxsplit+1]
+ limited_parts.reverse()
+ return limited_parts
+ else:
+ return parts
+ except:
+ return text.rsplit(pattern, maxsplit=maxsplit)
+
####menemkan karakter diluar tanda kutipp
def count_character_outside_quotes(input_string, character):
"""
From 5de4c074f19e09a6b207d22db00478a5eda02188 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 8 Sep 2024 10:55:07 +0700
Subject: [PATCH 42/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/supernano.py b/supernano.py
index cb4a863..b3bba00 100644
--- a/supernano.py
+++ b/supernano.py
@@ -398,7 +398,7 @@ def __init__(self, start_path="."):
def create_button(module_name):
button = PlainButton(module_name)
- urwid.connect_signal(button, "click", self.inspect_module, module_name)
+ urwid.connect_signal(button, "click", self.inspect_module, user_args=[module_name])
return urwid.AttrMap(button, None, focus_map="reversed")
@@ -633,7 +633,7 @@ def create_modules_menus(self, listmodulename: list):
def create_button(module_name):
button = PlainButton(module_name)
- urwid.connect_signal(button, "click", self.inspect_module, module_name)
+ urwid.connect_signal(button, "click", self.inspect_module, user_args=[module_name])
return urwid.AttrMap(button, None, focus_map="reversed")
From 5a7aaa003d8760f2fbbd5f82eb832046e3dfe727 Mon Sep 17 00:00:00 2001
From: LcferShell <71859305+LcfherShell@users.noreply.github.com>
Date: Sun, 8 Sep 2024 13:38:31 +0700
Subject: [PATCH 43/43] supernano.py
Signed-off-by: LcferShell <71859305+LcfherShell@users.noreply.github.com>
---
supernano.py | 70 ++++++++++++++++++++++++++--------------------------
1 file changed, 35 insertions(+), 35 deletions(-)
diff --git a/supernano.py b/supernano.py
index b3bba00..1ae1200 100644
--- a/supernano.py
+++ b/supernano.py
@@ -1,11 +1,9 @@
-import urwid
-
-import pyperclip
-
import os, sys, shutil, logging, time, threading, argparse
-
+if getattr(sys, 'frozen', False):
+ __file__ = sys.executable
from datetime import datetime
-
+import urwid
+import pyperclip
try:
from libs.helperegex import findpositions, rreplace
@@ -97,8 +95,9 @@
fileloogiing = os.path.join(thisfolder, "cache", "file_browser.log").replace("\\", "/")
-
-
+if not os.path.isdir(os.path.join(thisfolder, "cache")):
+ os.mkdir(os.path.join(thisfolder, "cache"))
+
if not os.path.isfile(fileloogiing):
open(fileloogiing, "a+")
@@ -198,12 +197,12 @@ def parse_args():
pass
else:
- logging.error(f"ERROR - {path} path cannot access")
+ logging.error("ERROR - {path} path cannot access".format(path=path))
exit()
else:
- logging.error(f"ERROR - {path} path does not exist")
+ logging.error("ERROR - {path} path does not exist".format(path=path))
exit()
@@ -232,7 +231,7 @@ def __init__(self, label, functions, file_path):
self.functions = functions
urwid.connect_signal(
- self, "click", self.on_single_click, user_args=self.file_path
+ self, "click", self.on_single_click, user_args=[self.file_path]
)
def on_single_click(self, button, user_data=None):
@@ -296,7 +295,7 @@ def update_line_numbers(self):
lines = text.splitlines()
- line_numbers = "\n".join([f"{i+1:>3}" for i in range(len(lines))])
+ line_numbers = "\n".join(["{:>3}".format(i + 1) for i in range(len(lines))])
self.line_numbers.set_text(line_numbers)
@@ -398,7 +397,7 @@ def __init__(self, start_path="."):
def create_button(module_name):
button = PlainButton(module_name)
- urwid.connect_signal(button, "click", self.inspect_module, user_args=[module_name])
+ urwid.connect_signal(button, "click", self.inspect_module, module_name)
return urwid.AttrMap(button, None, focus_map="reversed")
@@ -633,7 +632,7 @@ def create_modules_menus(self, listmodulename: list):
def create_button(module_name):
button = PlainButton(module_name)
- urwid.connect_signal(button, "click", self.inspect_module, user_args=[module_name])
+ urwid.connect_signal(button, "click", self.inspect_module, module_name)
return urwid.AttrMap(button, None, focus_map="reversed")
@@ -648,7 +647,7 @@ def inspect_module(self, button, module_name):
keys = result.keys()
if "classes" in keys or "functions" in keys or "variables" in keys:
- result_text = f"Module: {result['module']}\n\nGlobal Variables:\n"
+ result_text = "Module: {modulen}\n\nGlobal Variables:\n".format(modulen = result['module'])
result_text += ", ".join(result["variables"])
@@ -657,7 +656,7 @@ def inspect_module(self, button, module_name):
for cls in result["classes"]:
if cls["name"]:
- result_text += f"Class: {cls['name']}\n"
+ result_text += "Class: {classname}\n".format(classname=cls['name'])
result_text += " Variables:\n"
@@ -670,14 +669,14 @@ def inspect_module(self, button, module_name):
for func in cls["functions"]:
result_text += (
- f" > {func['name']}{func['params']}\n\n"
+ " > {funcname}{parms}\n\n".format(funcname=func['name'], parms=func['params'])
)
for funcs in result["functions"]:
if funcs["name"]:
- result_text += f"\nFunction: {funcs['name']}\n"
+ result_text += "\nFunction: {funcname}\n".format(funcname=funcs['name'])
- result_text += f" > {funcs['name']}{funcs['params']}\n\n"
+ result_text += " > {funcname}{parms}\n\n".format(funcname=funcs['name'], parms=funcs['params'])
self.Text_Deinspect_modules_from_package_Python.set_text(
result_text
@@ -773,7 +772,7 @@ def get_file_list(self):
files.append(urwid.AttrMap(button, None, focus_map="reversed"))
- for f in os.listdir(f"{self.current_path}"):
+ for f in os.listdir("{msg}".format(msg=self.current_path)):
if os.path.isdir(resolve_relative_path(self.current_path, f)):
f = f + "/"
@@ -787,6 +786,7 @@ def get_file_list(self):
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def renameORcreatedPOP(self):
+ "Menyiapkan konten dan layout untuk menu popup rename dan created"
select = urwid.Edit("Search or Create", "")
replaces = SaveableEdit("Replace ", "")
@@ -794,7 +794,7 @@ def on_save(button, *args):
slect = select.get_edit_text().strip()
if slect.__len__() <= 0:
return
- getselect = [f for f in os.listdir(f"{self.current_path}") if slect in f]
+ getselect = [f for f in os.listdir("{msg}".format(msg=self.current_path)) if slect in f]
if getselect and replaces.get_edit_text():
_y = replaces.get_edit_text().strip()
if isvalidate_folder(_y):
@@ -805,9 +805,9 @@ def on_save(button, *args):
selecrepcae = resolve_relative_path(self.current_path, _y)
if os.path.isdir(selecfolder) or os.path.isfile(selecfolder):
os.rename(selecfolder, selecrepcae)
- ms = str(f"Success renaming item")
+ ms = str("Success renaming item")
except:
- ms = str(f"Failed renaming item: {getselect[0]}")
+ ms = str("Failed renaming item: {msg}".format(msg=getselect[0]))
else:
ms = str("Item to rename not found")
else:
@@ -897,7 +897,7 @@ def handle_input(self, key):
elif key in ("f1", "ctrl e", "ctrl E"):
self.current_focus = 1 if self.current_focus == 0 else 0
- self.status_msg_footer_text.set_text(f"focus {self.current_focus}")
+ self.status_msg_footer_text.set_text("focus {msg}".format(msg=self.current_focus))
@complex_handle_errors(loggering=logging, nomessagesNormal=False)
def get_current_edit(self):
@@ -1075,7 +1075,7 @@ def save_file(self):
if self.current_file_name:
file_path = os.path.join(self.current_path, self.current_file_name)
-
+
if os.path.isfile(file_path):
try:
with open(
@@ -1168,7 +1168,7 @@ def highlight_text(self, search_text):
# Pisahkan teks menjadi sebelum, pencarian, dan sesudahnya
- for x in findpositions(f"{search_text}", text):
+ for x in findpositions("{msg}".format(msg=search_text), text):
if x:
_x = list(x)
@@ -1229,12 +1229,12 @@ def in_search_(self, button):
if isvalidate_folder(_y):
search_query = str(create_file_or_folder(search_query))
else:
- search_query = "{search_query} is Failed"
+ search_query = "{search} is Failed".format(search=search_query)
else:
if isvalidate_filename(_y):
search_query = str(create_file_or_folder(search_query))
else:
- search_query = "{search_query} is Failed"
+ search_query = "{search} is Failed".format(search=search_query)
self.update_ui()
@@ -1248,7 +1248,7 @@ def in_search_(self, button):
self.file_list[:] = self.get_file_list()
self.status_msg_footer_text.set_text(
- f"Search results for '{search_query}'"
+ "Search results for {msg}".format(msg=search_query)
)
else:
@@ -1272,14 +1272,14 @@ def in_search_(self, button):
self.file_list[:] = self.create_file_list(search_resultsFile)
self.status_msg_footer_text.set_text(
- f"Search results for '{search_query}'"
+ "Search results for {msg}".format(msg=search_query)
)
elif search_resultsFile:
self.file_list[:] = self.create_file_list(search_resultsFile)
self.status_msg_footer_text.set_text(
- f"Search results for '{search_query}'"
+ "Search results for {msg}".format(msg=search_query)
)
else:
@@ -1291,14 +1291,14 @@ def in_search_(self, button):
self.file_list[:] = self.get_file_list()
self.status_msg_footer_text.set_text(
- f"Search results for '{search_query}'"
+ "Search results for {msg}".format(msg=search_query)
)
elif search_resultsHighlight_Text and not search_query.startswith(
"@[files]"
):
self.status_msg_footer_text.set_text(
- f"Search results for '{search_query}' {search_resultsHighlight_Text}"
+ "Search results for '{msg}' {msg1}".format(msg=search_query, msg1=search_resultsHighlight_Text)
)
else:
@@ -1339,7 +1339,7 @@ def in_search_(self, button):
os.rename(oldfilesorfolder, newplace)
self.status_msg_footer_text.set_text(
- f"Rename {getREName[0]} success"
+ "Rename {msg} success".format(msg=getREName[0])
)
self.update_ui()
@@ -1351,7 +1351,7 @@ def in_search_(self, button):
else:
self.status_msg_footer_text.set_text(
- f"Search results for {search_query}"
+ "Search results for {msg}".format(msg=search_query)
)
else: