diff --git a/.gitignore b/.gitignore index d785d51..7a844bb 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ cmake-build-* ke kge + +# trashme is a test file generated to test the editor on without ruining +# other files. +trashme.txt diff --git a/Buffer.cc b/Buffer.cc index f6dc509..c625e05 100644 --- a/Buffer.cc +++ b/Buffer.cc @@ -23,6 +23,7 @@ #include +#include #include #include #include @@ -31,6 +32,9 @@ #include "Buffer.h" +static constexpr auto statusOK = Buffer::FileStatus::FileStatusOK; + + static std::string anonymousName() { @@ -45,7 +49,7 @@ anonymousName() Buffer::Buffer() - : dirty(false), name(anonymousName()) + : dirty(false), name(anonymousName()), status(statusOK) { } @@ -61,9 +65,14 @@ Buffer::Buffer(std::filesystem::path fPath) : dirty(false), path(OptString(fPath.string())) { this->name = fPath.filename().string(); - this->Cursor() = Cursor(); - + this->cursor = Cursor(); this->file = OptFile(fPath.string()); + if (this->Exists()) { + std::cout << "file exists, refreshing\n"; + /// \todo Should I signal an error here, or is it + /// okay for this to be a best-effort thing? + this->status = this->Refresh(); + } } @@ -106,6 +115,7 @@ Buffer::Flush(OptString altPath) return Buffer::FileStatus::FileStatusInvalidPermissions; } + realFile.MarkReadOnly(); return Buffer::FileStatus::FileStatusIOFailed; } @@ -131,13 +141,17 @@ Buffer::Flush(OptString altPath) Buffer::FileStatus Buffer::Refresh() { + /// We can't actually refresh a virtual buffer. Unlike flush, + /// it doesn't make sense to Refresh from an alternate file. if (this->IsVirtual()) { + std::cerr << "[!] virtual file, bailing\n"; return Buffer::FileStatus::FileStatusVirtual; } auto realFile = this->file.value(); auto handle = realFile.Refresh(); if (!handle) { + std::cerr << "[!] file doesn't exist\n"; return FileStatus::FileStatusNonExistent; } @@ -147,8 +161,11 @@ Buffer::Refresh() /// \todo Moral quandary: if the file disappears from disk, we /// probably want to know that, but maybe clearing the - /// contents first isn't the right move. + /// contents first isn't the right move. Maybe mark the + /// file as ReadOnly? if (!realHandle->good()) { + std::cerr << "[!] handle isn't good"; + realHandle->close(); delete realHandle; return FileStatus::FileStatusIOFailed; } @@ -167,6 +184,7 @@ Buffer::Refresh() status = FileStatus::FileStatusIOFailed; } realHandle->close(); + delete realHandle; return status; } @@ -246,3 +264,52 @@ Buffer::FileStatusToString(Buffer::FileStatus status) } } + + +bool +Buffer::Exists() +{ + if (!this->file) { + return false; + } + + return this->file.value().Exists(); +} + + + +void +Buffer::PrintBufferStatus(std::ostream &os) +{ + os << "buffer:<" << this->name << ">["; + if (this->IsVirtual()) { + os << "virt]"; + } else { + os << "file:"; + if (this->Exists()) { + os << "real"; + } else { + os << "new"; + } + os << ":"; + auto &realFile = this->file.value(); + if (realFile.IsReadWriteable()) { + os << "rw"; + } else if (realFile.IsReadable()) { + os << "r-"; + } else if (realFile.IsWriteable()) { + os << "-w"; + } else { + os << " "; + } + + if (realFile.IsReadOnly()) { + os << "@"; + } else { + os << " "; + } + + os << ":" << this->Size() << " + } + return os; +} diff --git a/Buffer.h b/Buffer.h index 0205a41..0f053b6 100644 --- a/Buffer.h +++ b/Buffer.h @@ -35,28 +35,38 @@ public: enum class FileStatus : uint8_t { /// The file operation succeeded correctly. FileStatusOK = 0, + + /// There was an error, but it wasn't handled correctly. + /// This is mostly for debugging purposes, and shouldn't + /// be seen by users. + /// + /// \detail This is set at the beginning of failable + /// operations, and indicates that the right + /// status wasn't set correctly. + FileStatusUnspecifiedError = 1, /// The file can't be written to because it is marked /// read-only. This refers to a buffer being marked as /// read-only, not to whether the underlying file is /// actually read-only. - FileStatusReadOnly = 1, + FileStatusReadOnly = 2, /// There was an I/O error trying to write to the file. - FileStatusIOFailed = 2, + FileStatusIOFailed = 3, /// The Buffer couldn't be flushed because it is a virtual /// buffer. If the user explicitly tried to save the buffer, /// they should be prompted for a path. - FileStatusVirtual = 3, + FileStatusVirtual = 4, /// The underlying file doesn't have the right permissions; /// for example, it's not writeable if the user is trying to /// write the file. - FileStatusInvalidPermissions = 4, + FileStatusInvalidPermissions = 5, /// The underlying file doesn't exist. - FileStatusNonExistent = 5, + FileStatusNonExistent = 6, + }; static std::string FileStatusToString(FileStatus status); @@ -100,7 +110,7 @@ public: void ChangePath(std::string newPath); - Cursor Cursor() + Cursor Where() { return this->cursor; } void MarkDirty(); @@ -109,16 +119,25 @@ public: size_t Size(); + /// Does this buffer have a current, on-disk file? + /// + /// \return True if this is a file buffer with an extant source + /// file. + bool Exists(); + + /// PrintBufferStatus is a debug tool to capture the current + /// buffer state. + void PrintBufferStatus(std::ostream &os); private: void clearContents(); - class Cursor cursor; - - bool dirty; - std::string name; - OptString path; - OptFile file; - BufferContents contents; + Cursor cursor; + bool dirty; + std::string name; + OptString path; + OptFile file; + BufferContents contents; + FileStatus status; }; diff --git a/File.cc b/File.cc index 4d94473..e1c8f79 100644 --- a/File.cc +++ b/File.cc @@ -64,10 +64,10 @@ OptOutFileStream File::Flush() return std::nullopt; } - return {new std::ofstream(this->path, this->mode|std::ios::trunc)}; + return {new std::ofstream(this->path, std::ios::out|std::ios::trunc)}; } - return {new std::ofstream(this->path, this->mode)}; + return {new std::ofstream(this->path, std::ios::out)}; } @@ -78,7 +78,7 @@ File::Refresh() return std::nullopt; } - return {new std::ifstream(this->path, this->mode)}; + return {new std::ifstream(this->path, std::ios::in)}; } diff --git a/File.h b/File.h index d6e22ca..7030a28 100644 --- a/File.h +++ b/File.h @@ -80,12 +80,10 @@ public: /// IsReadWriteable checks whether the current user has both /// read and write permissions on the file. bool IsReadWriteable(); - private: std::filesystem::path path; bool readOnly; - std::ios::openmode mode; }; diff --git a/Makefile b/Makefile index 6d62294..59ee0e5 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,36 @@ -.PHONY: all -all: - mkdir -p build && cd build && cmake .. && make +### quick Makefile to build ke in the top-level. It will never be used +### kge; ke doesn't use any non-system libraries. vim, emacs, and joe +### all support building from a Makefile in the root directory of the +### project, so it's useful to quickly test builds when editing remote. +CC := clang +CXX := clang++ +CXXFLAGS := -Werror -Wall -Wextra -g -fsanitize=address -std=c++17 +LDFLAGS := +SOURCES := Buffer.cc Buffer.h \ + Cursor.cc Cursor.h \ + Defs.cc Defs.h \ + File.cc File.h \ + main.cc +OBJS := $(patsubst %.cc,%.o,$(filter %.cc,$(SOURCES))) +TARGET := ke + +.PHONY: all +all: tags trashme $(TARGET) + ./$(TARGET) trashme.txt + +.PHONY: trashme +trashme: + cp Makefile trashme.txt + +.PHONY: cmake +cmake: build + cd build && cmake --config Debug --build .. + +build: + mkdir -p build + +# TODO: run these from ./scripts/install-dependencies.sh PHONY: deps deps: sudo apt-get install doxygen scdoc @@ -12,4 +41,20 @@ gui-deps: .PHONY: clean: + rm -f *.o $(TARGET) + +tags: $(SOURCES) + ctags --declarations -o $@ $(SOURCES) + +ke: $(OBJS) + $(CXX) -o $@ $(CXXFLAGS) $(LDFLAGS) $(OBJS) + + +.PHONY: cclean +cclean: rm -r build cmake-build-* + +print-%: ; @echo '$(subst ','\'',$*=$($*))' + +%.o:%.cc + $(CXX) -c -o $@ $(CXXFLAGS) $< diff --git a/main.cc b/main.cc index 444e48e..0a2aa32 100644 --- a/main.cc +++ b/main.cc @@ -56,11 +56,11 @@ main(int argc, char *argv[]) for (int i = 1; i < argc; i++) { std::filesystem::path path(argv[i]); - std::cout << "[+] target: " << path << "\n "; + std::cout << "[+] target: " << path << "\n"; auto buffer = Buffer(path); std::cout << "\t[+] created buffer " << buffer.Name() << "\n"; - auto status = buffer.Refresh(); + /* if (!Buffer::StatusOK(status)) { std::cerr << "[!] failed to read buffer "; std::cerr << buffer.Name() << "\n"; @@ -69,6 +69,7 @@ main(int argc, char *argv[]) std::cerr << "\n"; continue; } + */ std::cout << "\t[+] loaded buffer " << buffer.Name() << " of " << buffer.Size() << " bytes.\n";