Fix buffer stomping; retool Makefile.
The Makefile is now useful for building ke(1) to aid in more rapid development. This works because ke doesn't (yet) need any special non-system libraries.
This commit is contained in:
parent
ba27e132e3
commit
7cec414e3d
|
@ -15,3 +15,7 @@ cmake-build-*
|
||||||
|
|
||||||
ke
|
ke
|
||||||
kge
|
kge
|
||||||
|
|
||||||
|
# trashme is a test file generated to test the editor on without ruining
|
||||||
|
# other files.
|
||||||
|
trashme.txt
|
||||||
|
|
75
Buffer.cc
75
Buffer.cc
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
@ -31,6 +32,9 @@
|
||||||
#include "Buffer.h"
|
#include "Buffer.h"
|
||||||
|
|
||||||
|
|
||||||
|
static constexpr auto statusOK = Buffer::FileStatus::FileStatusOK;
|
||||||
|
|
||||||
|
|
||||||
static std::string
|
static std::string
|
||||||
anonymousName()
|
anonymousName()
|
||||||
{
|
{
|
||||||
|
@ -45,7 +49,7 @@ anonymousName()
|
||||||
|
|
||||||
|
|
||||||
Buffer::Buffer()
|
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()))
|
: dirty(false), path(OptString(fPath.string()))
|
||||||
{
|
{
|
||||||
this->name = fPath.filename().string();
|
this->name = fPath.filename().string();
|
||||||
this->Cursor() = Cursor();
|
this->cursor = Cursor();
|
||||||
|
|
||||||
this->file = OptFile(fPath.string());
|
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;
|
return Buffer::FileStatus::FileStatusInvalidPermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
realFile.MarkReadOnly();
|
||||||
return Buffer::FileStatus::FileStatusIOFailed;
|
return Buffer::FileStatus::FileStatusIOFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,13 +141,17 @@ Buffer::Flush(OptString altPath)
|
||||||
Buffer::FileStatus
|
Buffer::FileStatus
|
||||||
Buffer::Refresh()
|
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()) {
|
if (this->IsVirtual()) {
|
||||||
|
std::cerr << "[!] virtual file, bailing\n";
|
||||||
return Buffer::FileStatus::FileStatusVirtual;
|
return Buffer::FileStatus::FileStatusVirtual;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto realFile = this->file.value();
|
auto realFile = this->file.value();
|
||||||
auto handle = realFile.Refresh();
|
auto handle = realFile.Refresh();
|
||||||
if (!handle) {
|
if (!handle) {
|
||||||
|
std::cerr << "[!] file doesn't exist\n";
|
||||||
return FileStatus::FileStatusNonExistent;
|
return FileStatus::FileStatusNonExistent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,8 +161,11 @@ Buffer::Refresh()
|
||||||
|
|
||||||
/// \todo Moral quandary: if the file disappears from disk, we
|
/// \todo Moral quandary: if the file disappears from disk, we
|
||||||
/// probably want to know that, but maybe clearing the
|
/// 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()) {
|
if (!realHandle->good()) {
|
||||||
|
std::cerr << "[!] handle isn't good";
|
||||||
|
realHandle->close();
|
||||||
delete realHandle;
|
delete realHandle;
|
||||||
return FileStatus::FileStatusIOFailed;
|
return FileStatus::FileStatusIOFailed;
|
||||||
}
|
}
|
||||||
|
@ -167,6 +184,7 @@ Buffer::Refresh()
|
||||||
status = FileStatus::FileStatusIOFailed;
|
status = FileStatus::FileStatusIOFailed;
|
||||||
}
|
}
|
||||||
realHandle->close();
|
realHandle->close();
|
||||||
|
delete realHandle;
|
||||||
|
|
||||||
return status;
|
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;
|
||||||
|
}
|
||||||
|
|
45
Buffer.h
45
Buffer.h
|
@ -35,28 +35,38 @@ public:
|
||||||
enum class FileStatus : uint8_t {
|
enum class FileStatus : uint8_t {
|
||||||
/// The file operation succeeded correctly.
|
/// The file operation succeeded correctly.
|
||||||
FileStatusOK = 0,
|
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
|
/// The file can't be written to because it is marked
|
||||||
/// read-only. This refers to a buffer being marked as
|
/// read-only. This refers to a buffer being marked as
|
||||||
/// read-only, not to whether the underlying file is
|
/// read-only, not to whether the underlying file is
|
||||||
/// actually read-only.
|
/// actually read-only.
|
||||||
FileStatusReadOnly = 1,
|
FileStatusReadOnly = 2,
|
||||||
|
|
||||||
/// There was an I/O error trying to write to the file.
|
/// 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
|
/// The Buffer couldn't be flushed because it is a virtual
|
||||||
/// buffer. If the user explicitly tried to save the buffer,
|
/// buffer. If the user explicitly tried to save the buffer,
|
||||||
/// they should be prompted for a path.
|
/// they should be prompted for a path.
|
||||||
FileStatusVirtual = 3,
|
FileStatusVirtual = 4,
|
||||||
|
|
||||||
/// The underlying file doesn't have the right permissions;
|
/// The underlying file doesn't have the right permissions;
|
||||||
/// for example, it's not writeable if the user is trying to
|
/// for example, it's not writeable if the user is trying to
|
||||||
/// write the file.
|
/// write the file.
|
||||||
FileStatusInvalidPermissions = 4,
|
FileStatusInvalidPermissions = 5,
|
||||||
|
|
||||||
/// The underlying file doesn't exist.
|
/// The underlying file doesn't exist.
|
||||||
FileStatusNonExistent = 5,
|
FileStatusNonExistent = 6,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::string FileStatusToString(FileStatus status);
|
static std::string FileStatusToString(FileStatus status);
|
||||||
|
@ -100,7 +110,7 @@ public:
|
||||||
|
|
||||||
void ChangePath(std::string newPath);
|
void ChangePath(std::string newPath);
|
||||||
|
|
||||||
Cursor Cursor()
|
Cursor Where()
|
||||||
{ return this->cursor; }
|
{ return this->cursor; }
|
||||||
|
|
||||||
void MarkDirty();
|
void MarkDirty();
|
||||||
|
@ -109,16 +119,25 @@ public:
|
||||||
|
|
||||||
size_t Size();
|
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:
|
private:
|
||||||
void clearContents();
|
void clearContents();
|
||||||
|
|
||||||
class Cursor cursor;
|
Cursor cursor;
|
||||||
|
bool dirty;
|
||||||
bool dirty;
|
std::string name;
|
||||||
std::string name;
|
OptString path;
|
||||||
OptString path;
|
OptFile file;
|
||||||
OptFile file;
|
BufferContents contents;
|
||||||
BufferContents contents;
|
FileStatus status;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
6
File.cc
6
File.cc
|
@ -64,10 +64,10 @@ OptOutFileStream File::Flush()
|
||||||
return std::nullopt;
|
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 std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {new std::ifstream(this->path, this->mode)};
|
return {new std::ifstream(this->path, std::ios::in)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
2
File.h
2
File.h
|
@ -80,12 +80,10 @@ public:
|
||||||
/// IsReadWriteable checks whether the current user has both
|
/// IsReadWriteable checks whether the current user has both
|
||||||
/// read and write permissions on the file.
|
/// read and write permissions on the file.
|
||||||
bool IsReadWriteable();
|
bool IsReadWriteable();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
std::filesystem::path path;
|
std::filesystem::path path;
|
||||||
bool readOnly;
|
bool readOnly;
|
||||||
std::ios::openmode mode;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
51
Makefile
51
Makefile
|
@ -1,7 +1,36 @@
|
||||||
.PHONY: all
|
### quick Makefile to build ke in the top-level. It will never be used
|
||||||
all:
|
### kge; ke doesn't use any non-system libraries. vim, emacs, and joe
|
||||||
mkdir -p build && cd build && cmake .. && make
|
### 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
|
PHONY: deps
|
||||||
deps:
|
deps:
|
||||||
sudo apt-get install doxygen scdoc
|
sudo apt-get install doxygen scdoc
|
||||||
|
@ -12,4 +41,20 @@ gui-deps:
|
||||||
|
|
||||||
.PHONY:
|
.PHONY:
|
||||||
clean:
|
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-*
|
rm -r build cmake-build-*
|
||||||
|
|
||||||
|
print-%: ; @echo '$(subst ','\'',$*=$($*))'
|
||||||
|
|
||||||
|
%.o:%.cc
|
||||||
|
$(CXX) -c -o $@ $(CXXFLAGS) $<
|
||||||
|
|
5
main.cc
5
main.cc
|
@ -56,11 +56,11 @@ main(int argc, char *argv[])
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
std::filesystem::path path(argv[i]);
|
std::filesystem::path path(argv[i]);
|
||||||
std::cout << "[+] target: " << path << "\n ";
|
std::cout << "[+] target: " << path << "\n";
|
||||||
auto buffer = Buffer(path);
|
auto buffer = Buffer(path);
|
||||||
|
|
||||||
std::cout << "\t[+] created buffer " << buffer.Name() << "\n";
|
std::cout << "\t[+] created buffer " << buffer.Name() << "\n";
|
||||||
auto status = buffer.Refresh();
|
/*
|
||||||
if (!Buffer::StatusOK(status)) {
|
if (!Buffer::StatusOK(status)) {
|
||||||
std::cerr << "[!] failed to read buffer ";
|
std::cerr << "[!] failed to read buffer ";
|
||||||
std::cerr << buffer.Name() << "\n";
|
std::cerr << buffer.Name() << "\n";
|
||||||
|
@ -69,6 +69,7 @@ main(int argc, char *argv[])
|
||||||
std::cerr << "\n";
|
std::cerr << "\n";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
std::cout << "\t[+] loaded buffer " << buffer.Name()
|
std::cout << "\t[+] loaded buffer " << buffer.Name()
|
||||||
<< " of " << buffer.Size() << " bytes.\n";
|
<< " of " << buffer.Size() << " bytes.\n";
|
||||||
|
|
Loading…
Reference in New Issue