editor_core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 368d94707fe992bf22535129bce424ae0dc6a88bc08a2a95fc61e316e3bf1951
4
- data.tar.gz: ea02c89dd36ac8a9c8cc67846930d6bfb16af72a1af6f6ab8cb36e5ab9b55c00
3
+ metadata.gz: ec52d9db82e48638cbc82762edbfc019f825beda5eafa66475f18254c69e1f4e
4
+ data.tar.gz: 354a5ae6fb35b9bb195df0ace5c579a1643b61dbd0e8449ce7428c62b9fefd98
5
5
  SHA512:
6
- metadata.gz: 9b8c159f7083783f53a5dc4ed149ff30998296da4b33a1dc3a86b733294f1a0fdbae1d2b7c6c8bbdf63744b38fe627b2d256117a34dac67b5796bb3d60558cd6
7
- data.tar.gz: 2c66bb04281d89ef2b144b1fa5f08bcaa3add598db1cf833a635d8e32686687d224ee9ab0418fb3ceeaca7598ee1ba3a04625b59fd5b300e5354b3b6101522dd
6
+ metadata.gz: 1d65ef8b4ca8ca3138a67e0684bd3f315569423d715fc6fff022edb2d1c3b717db1efedfb4892711992107056c9afe29e30114beef2fb7c4d3c5350ea3ea8ba1
7
+ data.tar.gz: 483e3d2feb7dda1c78b5634612b92eb3f946c86921ae7898a81a3c73b665fd2d26c1edac0a724faed232b4ee190fcc1c3a1f37f72e979862132c95c400262775
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ *~
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # EditorCore
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/editor_core`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Very basic core concepts for an editor. Used in the next iteration of
4
+ [Re](https://github.com/vidarh/re)
4
5
 
5
- TODO: Delete this and the text above, and describe your gem
6
6
 
7
7
  ## Installation
8
8
 
@@ -32,7 +32,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
32
 
33
33
  ## Contributing
34
34
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/editor_core.
35
+ Bug reports and pull requests are welcome on GitHub at
36
+ https://github.com/vidarh/editor_core.
36
37
 
37
38
 
38
39
  ## License
@@ -0,0 +1,133 @@
1
+ require 'drb/observer'
2
+ require 'date'
3
+
4
+ require_relative 'history'
5
+
6
+ module EditorCore
7
+ class Buffer
8
+
9
+ include DRb::DRbObservable
10
+
11
+ attr_reader :name, :created_at, :history
12
+ attr_accessor :buffer_id
13
+ attr_writer :created_at
14
+
15
+ def initialize(id,name, lines, created_at = 0)
16
+ @buffer_id = id # An id for this buffer unique for this session
17
+ @name = name
18
+ @history = History.new
19
+ @lines = lines
20
+ @created_at = case created_at
21
+ when Numeric
22
+ Time.at(created_at)
23
+ when Time
24
+ created_at
25
+ when String
26
+ DateTime.parse(created_at)
27
+ else
28
+ raise "Unknown time format: #{created_at}"
29
+ end
30
+ end
31
+
32
+ def as_json(options = { })
33
+ {
34
+ "json_class" => self.class.name,
35
+ "data" => {
36
+ "id" => @buffer_id,
37
+ "name" => @name,
38
+ "lines" => @lines,
39
+ "created_at" => Time.at(created_at.to_i)
40
+ }
41
+ }
42
+ end
43
+
44
+ def to_json(*a)
45
+ as_json.to_json(*a)
46
+ end
47
+
48
+ def self.json_create(o)
49
+ d = o["data"]
50
+ new(d["id"], d["name"], d["lines"], (d["created_at"] || d["modified_at"] || 0))
51
+ end
52
+
53
+ def lines_count
54
+ @lines.size
55
+ end
56
+
57
+ def line_length(row)
58
+ @lines[row]&.size || 0
59
+ end
60
+
61
+ def replace_contents(cursor,new_contents)
62
+ modify(cursor, 0..-1) {|l| new_contents }
63
+ end
64
+
65
+ def insert(cursor, char)
66
+ modify(cursor, cursor.row) {|l| l.insert(cursor.col,char) }
67
+ end
68
+
69
+ def delete(cursor, from, to =nil)
70
+ to ||= from
71
+ modify(cursor, cursor.row) {|l| l[from..to] = ''; l }
72
+ end
73
+
74
+ def break_line(cursor)
75
+ modify(cursor, cursor.row..cursor.row) do |l|
76
+ [l[0][0...cursor.col], l[0][cursor.col..-1]]
77
+ end
78
+ end
79
+
80
+ def join_lines(cursor,offset=0)
81
+ row=cursor.row+offset
82
+ modify(cursor, row..row+1) {|l| l.join }
83
+ end
84
+
85
+ def indent(cursor, row, pos)
86
+ modify(cursor,row) do |line|
87
+ (" "*pos)+line.lstrip
88
+ end
89
+ end
90
+
91
+ def lines(r)
92
+ @lines[r]
93
+ end
94
+
95
+ def can_undo?
96
+ @history.can_undo?
97
+ end
98
+
99
+ def can_redo?
100
+ @history.can_redo?
101
+ end
102
+
103
+ def undo(old_cursor)
104
+ cursor, rowrange, rows = @history.undo_snapshot
105
+ store_snapshot(old_cursor,rowrange, false)
106
+ cursor, rowrange, rows = @history.undo
107
+ @lines[rowrange] = rows
108
+ cursor
109
+ end
110
+
111
+ def redo
112
+ cursor, rowrange, rows = @history.redo
113
+ @lines[rowrange] = rows
114
+ cursor
115
+ end
116
+
117
+ private
118
+
119
+ def store_snapshot(cursor, rowrange, advance = true)
120
+ @history.save([cursor, rowrange, @lines[rowrange]], advance)
121
+ end
122
+
123
+ def modify(cursor, rowrange)
124
+ lines = @lines[rowrange].dup
125
+ lines ||= ""
126
+ new_lines = yield(lines)
127
+ store_snapshot(cursor, rowrange, true)
128
+ @lines[rowrange] = new_lines
129
+ changed
130
+ notify_observers(self)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,189 @@
1
+ #
2
+ # # Core
3
+ #
4
+ # Generic editor functionality that only depends on `Buffer`, `Cursor`
5
+ # and bare minimum of `View`
6
+ #
7
+ # Functionality that goes here should *only* be "objective" behaviours.
8
+ # E.g. what it means to move a cursor to the right is mostly objective.
9
+ # What should happen on enter probably isn't (may or may not include
10
+ # indenting the next line, may or may not involve stripping trailing
11
+ # whitespace, etc.). Methods for the objective, constituent parts of
12
+ # subjective/opinionated operations *can* be added here (e.g. an "enter"
13
+ # method which defers the subjective parts to a block or some hook
14
+ # mechanism could.
15
+ #
16
+ # Knows nothing of configuration or "fancier" options, which should
17
+ # generally be handled in a subclass
18
+ #
19
+ # `@view` must support `#top`/`#top=` and `#height` to control the
20
+ # viewable portion.
21
+ #
22
+ module EditorCore
23
+ class Core
24
+ attr_accessor :cursor, :buffer, :view
25
+
26
+ # ## Basic cursor movement ##
27
+
28
+ def move(row, col); @cursor = cursor.move(buffer, row.to_i, col.to_i) end
29
+ def right(off=1); @cursor = cursor.right(buffer,off.to_i) end
30
+ def left(off=1); @cursor = cursor.left(buffer,off.to_i) end
31
+ def up(off=1); @cursor = cursor.up(buffer,off.to_i) end
32
+ def down(off=1); @cursor = cursor.down(buffer,off.to_i) end
33
+
34
+ def line_home; @cursor = cursor.line_home end
35
+ def line_end; @cursor = cursor.line_end(buffer) end
36
+
37
+ # ## Querying ##
38
+
39
+ def current_line
40
+ @buffer.lines(cursor.row)
41
+ end
42
+
43
+ def get_after
44
+ @buffer.lines(cursor.row)[@cursor.col..-1]
45
+ end
46
+
47
+ ### View-relative cursor movement ##
48
+
49
+ def buffer_home
50
+ view_home
51
+ @cursor = cursor.move(buffer, 0, cursor.col)
52
+ end
53
+
54
+ def buffer_end
55
+ view_end
56
+ @cursor = cursor.move(buffer, buffer.lines_count, cursor.col)
57
+ end
58
+
59
+ def page_down
60
+ lines = view_down(view.height-1)
61
+ @cursor = cursor.down(buffer, lines)
62
+ end
63
+
64
+ def page_up
65
+ @cursor = cursor.up(buffer, view_up(view.height-1))
66
+ end
67
+
68
+ # View movement
69
+ def view_up(offset = 1)
70
+ oldtop = view.top
71
+ view.top -= offset
72
+ view.top = 0 if view.top < 0
73
+ oldtop - view.top
74
+ end
75
+
76
+ def view_down(offset = 1)
77
+ oldtop = view.top
78
+ view.top += offset
79
+ if view.top > buffer.lines_count
80
+ view.top = buffer.lines_count
81
+ end
82
+ view.top - oldtop
83
+ end
84
+
85
+ def view_home
86
+ view.top = 0
87
+ end
88
+
89
+ def view_end
90
+ view.top = buffer.lines_count - view.height
91
+ end
92
+
93
+ # ## Complex navigation ##
94
+ def next_word
95
+ line = current_line
96
+ c = cursor.col
97
+ m = line.length
98
+ while c<m && line[c]&.match(/[^a-zA-Z]/)
99
+ c += 1
100
+ end
101
+ if c >= m
102
+ # FIXME: Pathological cases can cause stack issue here.
103
+ @cursor = Cursor.new(cursor.row,m)
104
+ right
105
+ return next_word
106
+ end
107
+
108
+ if run = line[c..-1]&.match(/([a-zA-Z]+)/)
109
+ c += run[0].length
110
+ #pry([line,c,run])
111
+ end
112
+ off = c - cursor.col
113
+ right(off)
114
+ off
115
+ end
116
+
117
+
118
+ def prev_word
119
+ return if cursor.col == 0
120
+ line = current_line
121
+ c = cursor.col
122
+ if c > 0
123
+ c -= 1
124
+ end
125
+ while c > 0 && line[c] && line[c].match(/[ \t]/)
126
+ c -= 1
127
+ end
128
+ while c > 0 && !(line[c-1].match(/[ \t\-]/))
129
+ c -= 1
130
+ end
131
+ off = cursor.col - c
132
+ @cursor = Cursor.new(cursor.row, c)
133
+ off
134
+ end
135
+
136
+ # ## Mutation ##
137
+
138
+ def delete_before
139
+ buffer.delete(cursor, 0, cursor.col)
140
+ line_home
141
+ end
142
+
143
+ def delete_after
144
+ buffer.delete(cursor, cursor.col,-1)
145
+ end
146
+
147
+ def delete
148
+ return if cursor.end_of_file?(buffer)
149
+
150
+ if cursor.end_of_line?(buffer)
151
+ buffer.join_lines(cursor)
152
+ else
153
+ buffer.delete(cursor, cursor.col)
154
+ end
155
+ end
156
+
157
+ def join_line
158
+ buffer.join_lines(cursor)
159
+ end
160
+
161
+ def backspace
162
+ return if cursor.beginning_of_file?
163
+
164
+ if cursor.col == 0
165
+ cursor_left = buffer.lines(cursor.row).size + 1
166
+ buffer.join_lines(cursor,-1)
167
+ cursor_left.times { left }
168
+ else
169
+ buffer.delete(cursor, cursor.col - 1)
170
+ left
171
+ end
172
+ end
173
+
174
+
175
+ def rstrip_line
176
+ line = current_line
177
+ stripped = current_line.rstrip
178
+ return if line.length == stripped.length
179
+ col = cursor.col
180
+ oldc = cursor
181
+ move(cursor.row, stripped.length)
182
+ delete_after
183
+ if col < stripped.length
184
+ @cursor = oldc
185
+ end
186
+ end
187
+
188
+ end
189
+ end
@@ -0,0 +1,72 @@
1
+
2
+ module EditorCore
3
+ class Cursor
4
+ attr_reader :row, :col
5
+
6
+ def initialize(row = 0, col = 0)
7
+ @row = row
8
+ @col = col
9
+ freeze
10
+ end
11
+
12
+ def move(buffer,row,col); self.class.new(row,col).clamp(buffer); end
13
+ def up(buffer, offset = 1); move(buffer, row-offset,col); end
14
+ def down(buffer, offset = 1); move(buffer, row+offset,col); end
15
+
16
+ def left(buffer, offset = 1)
17
+ return Cursor.new(row, col - offset) if offset <= col
18
+ return self if beginning_of_file?
19
+ return move(buffer,row-1,buffer.line_length(row-1))
20
+ end
21
+
22
+ def right(buffer, offset = 1)
23
+ #buffer.right(self,offset)
24
+ return Cursor.new(row, col + offset).clamp(buffer) unless end_of_line?(buffer)
25
+ return self if final_line?(buffer)
26
+ Cursor.new(row + 1, 0)
27
+ end
28
+
29
+ def clamp(buffer)
30
+ row = @row.clamp(0, buffer.lines_count - 1)
31
+ # FIXME: Is this `buffer.lines` check needed? It ought not to be.
32
+ if !buffer.lines(row)
33
+ col = 0
34
+ else
35
+ col = @col.clamp(0, buffer.line_length(row))
36
+ end
37
+ Cursor.new(row,col)
38
+ end
39
+
40
+ def enter(buffer)
41
+ down(buffer).line_home
42
+ end
43
+
44
+ def line_home
45
+ Cursor.new(row, 0)
46
+ end
47
+
48
+ def line_end(buffer)
49
+ Cursor.new(row, buffer.line_length(row))
50
+ end
51
+
52
+ def end_of_line?(buffer)
53
+ if buffer.lines(row)
54
+ col == buffer.line_length(row)
55
+ else
56
+ true
57
+ end
58
+ end
59
+
60
+ def final_line?(buffer)
61
+ row == buffer.lines_count - 1
62
+ end
63
+
64
+ def end_of_file?(buffer)
65
+ final_line?(buffer) && end_of_line?(buffer)
66
+ end
67
+
68
+ def beginning_of_file?
69
+ row == 0 && col == 0
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,42 @@
1
+
2
+ module EditorCore
3
+ class History
4
+ def initialize
5
+ @snapshots = []
6
+ @current = -1
7
+ end
8
+
9
+ def save(data, advance = true)
10
+ snapshots[@current+1] = data
11
+ @current += 1 if advance
12
+ end
13
+
14
+ def can_undo?
15
+ !undo_snapshot.nil?
16
+ end
17
+
18
+ def undo
19
+ undo_snapshot.tap { @current -= 1 }
20
+ end
21
+
22
+ def can_redo?
23
+ !redo_snapshot.nil?
24
+ end
25
+
26
+ def redo
27
+ redo_snapshot.tap { @current += 1 }
28
+ end
29
+
30
+ def undo_snapshot
31
+ snapshots[current] if current >= 0
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :snapshots, :current
37
+
38
+ def redo_snapshot
39
+ snapshots[current + 2]
40
+ end
41
+ end
42
+ end
@@ -1,3 +1,3 @@
1
1
  module EditorCore
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: editor_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vidar Hokstad
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-25 00:00:00.000000000 Z
11
+ date: 2021-11-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -28,6 +28,10 @@ files:
28
28
  - bin/setup
29
29
  - editor_core.gemspec
30
30
  - lib/editor_core.rb
31
+ - lib/editor_core/buffer.rb
32
+ - lib/editor_core/core.rb
33
+ - lib/editor_core/cursor.rb
34
+ - lib/editor_core/history.rb
31
35
  - lib/editor_core/version.rb
32
36
  homepage: https://github.com/vidarh/editor_core
33
37
  licenses: