patch 8.1.0735: cannot handle binary data

Problem:    Cannot handle binary data.
Solution:   Add the Blob type. (Yasuhiro Matsumoto, closes #3638)
This commit is contained in:
Bram Moolenaar
2019-01-12 22:47:31 +01:00
parent e3c74d249a
commit 6e5ea8d2a9
27 changed files with 1354 additions and 114 deletions

View File

@@ -72,6 +72,10 @@ Job Used for a job, see |job_start()|. *Job* *Jobs*
Channel Used for a channel, see |ch_open()|. *Channel* *Channels* Channel Used for a channel, see |ch_open()|. *Channel* *Channels*
Blob Binary Large Object. Stores any sequence of bytes. *Blob*
Example: 0zFF00ED015DAF
0z is an empty Blob.
The Number and String types are converted automatically, depending on how they The Number and String types are converted automatically, depending on how they
are used. are used.
@@ -124,7 +128,8 @@ Note that " " and "0" are also non-empty strings, thus considered to be TRUE.
A List, Dictionary or Float is not a Number or String, thus evaluate to FALSE. A List, Dictionary or Float is not a Number or String, thus evaluate to FALSE.
*E745* *E728* *E703* *E729* *E730* *E731* *E908* *E910* *E913* *E745* *E728* *E703* *E729* *E730* *E731* *E908* *E910* *E913*
List, Dictionary, Funcref, Job and Channel types are not automatically *E974* *E975* *E976*
List, Dictionary, Funcref, Job, Channel and Blob types are not automatically
converted. converted.
*E805* *E806* *E808* *E805* *E806* *E808*
@@ -1017,6 +1022,12 @@ just above. Also see |sublist| below. Examples: >
:let l = mylist[4:4] " List with one item :let l = mylist[4:4] " List with one item
:let l = mylist[:] " shallow copy of a List :let l = mylist[:] " shallow copy of a List
If expr8 is a |Blob| this results in a new |Blob| with the bytes in the
indexes expr1a and expr1b, inclusive. Examples: >
:let b = 0zDEADBEEF
:let bs = b[1:2] " 0zADBE
:let bs = b[] " copy ov 0zDEADBEEF
Using expr8[expr1] or expr8[expr1a : expr1b] on a |Funcref| results in an Using expr8[expr1] or expr8[expr1a : expr1b] on a |Funcref| results in an
error. error.
@@ -1156,6 +1167,14 @@ of 'encoding'.
Note that "\000" and "\x00" force the end of the string. Note that "\000" and "\x00" force the end of the string.
blob-literal *blob-literal* *E973* *E977* *E978*
------------
Hexadecimal starting with 0z or 0Z, with an arbitrary number of bytes.
The sequence must be an even number of hex characters. Example: >
:let b = 0zFF00ED015DAF
literal-string *literal-string* *E115* literal-string *literal-string* *E115*
--------------- ---------------
'string' string constant *expr-'* 'string' string constant *expr-'*
@@ -1911,6 +1930,8 @@ v:t_none Value of None type. Read-only. See: |type()|
v:t_number Value of Number type. Read-only. See: |type()| v:t_number Value of Number type. Read-only. See: |type()|
*v:t_string* *t_string-variable* *v:t_string* *t_string-variable*
v:t_string Value of String type. Read-only. See: |type()| v:t_string Value of String type. Read-only. See: |type()|
*v:t_blob* *t_blob-variable*
v:t_blob Value of Blob type. Read-only. See: |type()|
*v:termresponse* *termresponse-variable* *v:termresponse* *termresponse-variable*
v:termresponse The escape sequence returned by the terminal for the |t_RV| v:termresponse The escape sequence returned by the terminal for the |t_RV|
@@ -2094,12 +2115,14 @@ ch_logfile({fname} [, {mode}]) none start logging channel activity
ch_open({address} [, {options}]) ch_open({address} [, {options}])
Channel open a channel to {address} Channel open a channel to {address}
ch_read({handle} [, {options}]) String read from {handle} ch_read({handle} [, {options}]) String read from {handle}
ch_readblob({handle} [, {options}])
Blob read Blob from {handle}
ch_readraw({handle} [, {options}]) ch_readraw({handle} [, {options}])
String read raw from {handle} String read raw from {handle}
ch_sendexpr({handle}, {expr} [, {options}]) ch_sendexpr({handle}, {expr} [, {options}])
any send {expr} over JSON {handle} any send {expr} over JSON {handle}
ch_sendraw({handle}, {string} [, {options}]) ch_sendraw({handle}, {expr} [, {options}])
any send {string} over raw {handle} any send {expr} over raw {handle}
ch_setoptions({handle}, {options}) ch_setoptions({handle}, {options})
none set options for {handle} none set options for {handle}
ch_status({handle} [, {options}]) ch_status({handle} [, {options}])
@@ -2239,8 +2262,8 @@ hlID({name}) Number syntax ID of highlight group {name}
hostname() String name of the machine Vim is running on hostname() String name of the machine Vim is running on
iconv({expr}, {from}, {to}) String convert encoding of {expr} iconv({expr}, {from}, {to}) String convert encoding of {expr}
indent({lnum}) Number indent of line {lnum} indent({lnum}) Number indent of line {lnum}
index({list}, {expr} [, {start} [, {ic}]]) index({object}, {expr} [, {start} [, {ic}]])
Number index in {list} where {expr} appears Number index in {object} where {expr} appears
input({prompt} [, {text} [, {completion}]]) input({prompt} [, {text} [, {completion}]])
String get input from the user String get input from the user
inputdialog({prompt} [, {text} [, {completion}]]) inputdialog({prompt} [, {text} [, {completion}]])
@@ -2249,7 +2272,7 @@ inputlist({textlist}) Number let the user pick from a choice list
inputrestore() Number restore typeahead inputrestore() Number restore typeahead
inputsave() Number save and clear typeahead inputsave() Number save and clear typeahead
inputsecret({prompt} [, {text}]) String like input() but hiding the text inputsecret({prompt} [, {text}]) String like input() but hiding the text
insert({list}, {item} [, {idx}]) List insert {item} in {list} [before {idx}] insert({object}, {item} [, {idx}]) List insert {item} in {object} [before {idx}]
invert({expr}) Number bitwise invert invert({expr}) Number bitwise invert
isdirectory({directory}) Number |TRUE| if {directory} is a directory isdirectory({directory}) Number |TRUE| if {directory} is a directory
islocked({expr}) Number |TRUE| if {expr} is locked islocked({expr}) Number |TRUE| if {expr} is locked
@@ -2339,7 +2362,7 @@ py3eval({expr}) any evaluate |python3| expression
pyxeval({expr}) any evaluate |python_x| expression pyxeval({expr}) any evaluate |python_x| expression
range({expr} [, {max} [, {stride}]]) range({expr} [, {max} [, {stride}]])
List items from {expr} to {max} List items from {expr} to {max}
readfile({fname} [, {binary} [, {max}]]) readfile({fname} [, {type} [, {max}]])
List get list of lines from file {fname} List get list of lines from file {fname}
reg_executing() String get the executing register name reg_executing() String get the executing register name
reg_recording() String get the recording register name reg_recording() String get the recording register name
@@ -2554,8 +2577,8 @@ winrestview({dict}) none restore view of current window
winsaveview() Dict save view of current window winsaveview() Dict save view of current window
winwidth({nr}) Number width of window {nr} winwidth({nr}) Number width of window {nr}
wordcount() Dict get byte/char/word statistics wordcount() Dict get byte/char/word statistics
writefile({list}, {fname} [, {flags}]) writefile({object}, {fname} [, {flags}])
Number write list of lines to file {fname} Number write |Blob| or |List| of lines to file
xor({expr}, {expr}) Number bitwise XOR xor({expr}, {expr}) Number bitwise XOR
@@ -3199,6 +3222,11 @@ ch_read({handle} [, {options}]) *ch_read()*
See |channel-more|. See |channel-more|.
{only available when compiled with the |+channel| feature} {only available when compiled with the |+channel| feature}
ch_readblob({handle} [, {options}]) *ch_readblob()*
Like ch_read() but reads binary data and returns a Blob.
See |channel-more|.
{only available when compiled with the |+channel| feature}
ch_readraw({handle} [, {options}]) *ch_readraw()* ch_readraw({handle} [, {options}]) *ch_readraw()*
Like ch_read() but for a JS and JSON channel does not decode Like ch_read() but for a JS and JSON channel does not decode
the message. For a NL channel it does not block waiting for the message. For a NL channel it does not block waiting for
@@ -3215,8 +3243,8 @@ ch_sendexpr({handle}, {expr} [, {options}]) *ch_sendexpr()*
{only available when compiled with the |+channel| feature} {only available when compiled with the |+channel| feature}
ch_sendraw({handle}, {string} [, {options}]) *ch_sendraw()* ch_sendraw({handle}, {expr} [, {options}]) *ch_sendraw()*
Send {string} over {handle}. Send string or Blob {expr} over {handle}.
Works like |ch_sendexpr()|, but does not encode the request or Works like |ch_sendexpr()|, but does not encode the request or
decode the response. The caller is responsible for the decode the response. The caller is responsible for the
correct contents. Also does not add a newline for a channel correct contents. Also does not add a newline for a channel
@@ -5375,17 +5403,21 @@ indent({lnum}) The result is a Number, which is indent of line {lnum} in the
When {lnum} is invalid -1 is returned. When {lnum} is invalid -1 is returned.
index({list}, {expr} [, {start} [, {ic}]]) *index()* index({object}, {expr} [, {start} [, {ic}]]) *index()*
Return the lowest index in |List| {list} where the item has a If {object} is a |List| return the lowest index where the item
value equal to {expr}. There is no automatic conversion, so has a value equal to {expr}. There is no automatic
the String "4" is different from the Number 4. And the number conversion, so the String "4" is different from the Number 4.
4 is different from the Float 4.0. The value of 'ignorecase' And the number 4 is different from the Float 4.0. The value
is not used here, case always matters. of 'ignorecase' is not used here, case always matters.
If {object} is |Blob| return the lowest index where the byte
value is equal to {expr}.
If {start} is given then start looking at the item with index If {start} is given then start looking at the item with index
{start} (may be negative for an item relative to the end). {start} (may be negative for an item relative to the end).
When {ic} is given and it is |TRUE|, ignore case. Otherwise When {ic} is given and it is |TRUE|, ignore case. Otherwise
case must match. case must match.
-1 is returned when {expr} is not found in {list}. -1 is returned when {expr} is not found in {object}.
Example: > Example: >
:let idx = index(words, "the") :let idx = index(words, "the")
:if index(numbers, 123) >= 0 :if index(numbers, 123) >= 0
@@ -5491,13 +5523,16 @@ inputsecret({prompt} [, {text}]) *inputsecret()*
typed on the command-line in response to the issued prompt. typed on the command-line in response to the issued prompt.
NOTE: Command-line completion is not supported. NOTE: Command-line completion is not supported.
insert({list}, {item} [, {idx}]) *insert()* insert({object}, {item} [, {idx}]) *insert()*
Insert {item} at the start of |List| {list}. When {object} is a |List| or a |Blob| insert {item} at the start
of it.
If {idx} is specified insert {item} before the item with index If {idx} is specified insert {item} before the item with index
{idx}. If {idx} is zero it goes before the first item, just {idx}. If {idx} is zero it goes before the first item, just
like omitting {idx}. A negative {idx} is also possible, see like omitting {idx}. A negative {idx} is also possible, see
|list-index|. -1 inserts just before the last item. |list-index|. -1 inserts just before the last item.
Returns the resulting |List|. Examples: >
Returns the resulting |List| or |Blob|. Examples: >
:let mylist = insert([2, 3, 5], 1) :let mylist = insert([2, 3, 5], 1)
:call insert(mylist, 4, -1) :call insert(mylist, 4, -1)
:call insert(mylist, 6, len(mylist)) :call insert(mylist, 6, len(mylist))
@@ -5763,6 +5798,7 @@ json_encode({expr}) *json_encode()*
used recursively: [] used recursively: []
Dict as an object (possibly null); when Dict as an object (possibly null); when
used recursively: {} used recursively: {}
Blob as an array of the individual bytes
v:false "false" v:false "false"
v:true "true" v:true "true"
v:none "null" v:none "null"
@@ -6947,16 +6983,18 @@ range({expr} [, {max} [, {stride}]]) *range()*
range(2, 0) " error! range(2, 0) " error!
< <
*readfile()* *readfile()*
readfile({fname} [, {binary} [, {max}]]) readfile({fname} [, {type} [, {max}]])
Read file {fname} and return a |List|, each line of the file Read file {fname} and return a |List|, each line of the file
as an item. Lines are broken at NL characters. Macintosh as an item. Lines are broken at NL characters. Macintosh
files separated with CR will result in a single long line files separated with CR will result in a single long line
(unless a NL appears somewhere). (unless a NL appears somewhere).
All NUL characters are replaced with a NL character. All NUL characters are replaced with a NL character.
When {binary} contains "b" binary mode is used: When {type} contains "b" binary mode is used:
- When the last line ends in a NL an extra empty list item is - When the last line ends in a NL an extra empty list item is
added. added.
- No CR characters are removed. - No CR characters are removed.
When {type} contains "B" a |Blob| is returned with the binary
data of the file unmodified.
Otherwise: Otherwise:
- CR characters that appear before a NL are removed. - CR characters that appear before a NL are removed.
- Whether the last line ends in a NL or not does not matter. - Whether the last line ends in a NL or not does not matter.
@@ -7132,6 +7170,16 @@ remove({list}, {idx} [, {end}]) *remove()*
Example: > Example: >
:echo "last item: " . remove(mylist, -1) :echo "last item: " . remove(mylist, -1)
:call remove(mylist, 0, 9) :call remove(mylist, 0, 9)
remove({blob}, {idx} [, {end}])
Without {end}: Remove the byte at {idx} from |Blob| {blob} and
return the byte.
With {end}: Remove bytes from {idx} to {end} (inclusive) and
return a |Blob| with these bytes. When {idx} points to the same
byte as {end} a |Blob| with one byte is returned. When {end}
points to a byte before {idx} this is an error.
Example: >
:echo "last byte: " . remove(myblob, -1)
:call remove(mylist, 0, 9)
remove({dict}, {key}) remove({dict}, {key})
Remove the entry from {dict} with key {key}. Example: > Remove the entry from {dict} with key {key}. Example: >
:echo "removed " . remove(dict, "one") :echo "removed " . remove(dict, "one")
@@ -7172,9 +7220,11 @@ resolve({filename}) *resolve()* *E655*
path name) and also keeps a trailing path separator. path name) and also keeps a trailing path separator.
*reverse()* *reverse()*
reverse({list}) Reverse the order of items in {list} in-place. Returns reverse({object})
{list}. Reverse the order of items in {object} in-place.
If you want a list to remain unmodified make a copy first: > {object} can be a |List| or a |Blob|.
Returns {object}.
If you want an object to remain unmodified make a copy first: >
:let revlist = reverse(copy(mylist)) :let revlist = reverse(copy(mylist))
round({expr}) *round()* round({expr}) *round()*
@@ -9518,6 +9568,7 @@ type({expr}) The result is a Number representing the type of {expr}.
None 7 |v:t_none| (v:null and v:none) None 7 |v:t_none| (v:null and v:none)
Job 8 |v:t_job| Job 8 |v:t_job|
Channel 9 |v:t_channel| Channel 9 |v:t_channel|
Blob 10 |v:t_blob|
For backward compatibility, this method can be used: > For backward compatibility, this method can be used: >
:if type(myvar) == type(0) :if type(myvar) == type(0)
:if type(myvar) == type("") :if type(myvar) == type("")
@@ -9865,14 +9916,17 @@ wordcount() *wordcount()*
*writefile()* *writefile()*
writefile({list}, {fname} [, {flags}]) writefile({object}, {fname} [, {flags}])
Write |List| {list} to file {fname}. Each list item is When {object} is a |List| write it to file {fname}. Each list
separated with a NL. Each list item must be a String or item is separated with a NL. Each list item must be a String
Number. or Number.
When {flags} contains "b" then binary mode is used: There will When {flags} contains "b" then binary mode is used: There will
not be a NL after the last list item. An empty item at the not be a NL after the last list item. An empty item at the
end does cause the last line in the file to end in a NL. end does cause the last line in the file to end in a NL.
When {object} is a |Blob| write the bytes to file {fname}
unmodified.
When {flags} contains "a" then append mode is used, lines are When {flags} contains "a" then append mode is used, lines are
appended to the file: > appended to the file: >
:call writefile(["foo"], "event.log", "a") :call writefile(["foo"], "event.log", "a")
@@ -10575,7 +10629,10 @@ This does NOT work: >
This cannot be used to set a byte in a String. You This cannot be used to set a byte in a String. You
can do that like this: > can do that like this: >
:let var = var[0:2] . 'X' . var[4:] :let var = var[0:2] . 'X' . var[4:]
< < When {var-name} is a |Blob| then {idx} can be the
length of the blob, in which case one byte is
appended.
*E711* *E719* *E711* *E719*
:let {var-name}[{idx1}:{idx2}] = {expr1} *E708* *E709* *E710* :let {var-name}[{idx1}:{idx2}] = {expr1} *E708* *E709* *E710*
Set a sequence of items in a |List| to the result of Set a sequence of items in a |List| to the result of

View File

@@ -191,6 +191,9 @@ VIM::Eval({expr}) Evaluates {expr} and returns (success, value) in list
A |List| is turned into a string by joining the items A |List| is turned into a string by joining the items
and inserting line breaks. and inserting line breaks.
*perl-Blob*
VIM::Blob({expr}) Return Blob literal string 0zXXXX from scalar value.
*perl-SetHeight* *perl-SetHeight*
Window->SetHeight({height}) Window->SetHeight({height})
Sets the Window height to {height}, within screen Sets the Window height to {height}, within screen

View File

@@ -110,6 +110,10 @@ Module Functions:
Vim::message({msg}) Vim::message({msg})
Displays the message {msg}. Displays the message {msg}.
*ruby-blob*
Vim::blob({arg})
Return Blob literal string from {arg}.
*ruby-set_option* *ruby-set_option*
Vim::set_option({arg}) Vim::set_option({arg})
Sets a vim option. {arg} can be any argument that the ":set" command Sets a vim option. {arg} can be any argument that the ":set" command

View File

@@ -696,6 +696,7 @@ CUIOBJ = $(OUTDIR)/iscygpty.o
OBJ = \ OBJ = \
$(OUTDIR)/arabic.o \ $(OUTDIR)/arabic.o \
$(OUTDIR)/beval.o \ $(OUTDIR)/beval.o \
$(OUTDIR)/blob.o \
$(OUTDIR)/blowfish.o \ $(OUTDIR)/blowfish.o \
$(OUTDIR)/buffer.o \ $(OUTDIR)/buffer.o \
$(OUTDIR)/charset.o \ $(OUTDIR)/charset.o \

View File

@@ -701,6 +701,7 @@ INCL = vim.h alloc.h arabic.h ascii.h ex_cmds.h farsi.h feature.h globals.h \
OBJ = \ OBJ = \
$(OUTDIR)\arabic.obj \ $(OUTDIR)\arabic.obj \
$(OUTDIR)\beval.obj \ $(OUTDIR)\beval.obj \
$(OUTDIR)\blob.obj \
$(OUTDIR)\blowfish.obj \ $(OUTDIR)\blowfish.obj \
$(OUTDIR)\buffer.obj \ $(OUTDIR)\buffer.obj \
$(OUTDIR)\charset.obj \ $(OUTDIR)\charset.obj \
@@ -1346,6 +1347,8 @@ $(OUTDIR)/arabic.obj: $(OUTDIR) arabic.c $(INCL)
$(OUTDIR)/beval.obj: $(OUTDIR) beval.c $(INCL) $(OUTDIR)/beval.obj: $(OUTDIR) beval.c $(INCL)
$(OUTDIR)/blob.obj: $(OUTDIR) blob.c $(INCL)
$(OUTDIR)/blowfish.obj: $(OUTDIR) blowfish.c $(INCL) $(OUTDIR)/blowfish.obj: $(OUTDIR) blowfish.c $(INCL)
$(OUTDIR)/buffer.obj: $(OUTDIR) buffer.c $(INCL) $(OUTDIR)/buffer.obj: $(OUTDIR) buffer.c $(INCL)
@@ -1616,6 +1619,7 @@ auto:
# End Custom Build # End Custom Build
proto.h: \ proto.h: \
proto/arabic.pro \ proto/arabic.pro \
proto/blob.pro \
proto/blowfish.pro \ proto/blowfish.pro \
proto/buffer.pro \ proto/buffer.pro \
proto/charset.pro \ proto/charset.pro \

View File

@@ -1577,6 +1577,7 @@ include testdir/Make_all.mak
BASIC_SRC = \ BASIC_SRC = \
arabic.c \ arabic.c \
beval.c \ beval.c \
blob.c \
blowfish.c \ blowfish.c \
buffer.c \ buffer.c \
charset.c \ charset.c \
@@ -1693,6 +1694,7 @@ OBJ_COMMON = \
objects/arabic.o \ objects/arabic.o \
objects/beval.o \ objects/beval.o \
objects/buffer.o \ objects/buffer.o \
objects/blob.o \
objects/blowfish.o \ objects/blowfish.o \
objects/crypt.o \ objects/crypt.o \
objects/crypt_zip.o \ objects/crypt_zip.o \
@@ -2943,6 +2945,9 @@ $(ALL_OBJ): objects/.dirstamp
objects/arabic.o: arabic.c objects/arabic.o: arabic.c
$(CCC) -o $@ arabic.c $(CCC) -o $@ arabic.c
objects/blob.o: blob.c
$(CCC) -o $@ blob.c
objects/blowfish.o: blowfish.c objects/blowfish.o: blowfish.c
$(CCC) -o $@ blowfish.c $(CCC) -o $@ blowfish.c
@@ -3395,6 +3400,10 @@ objects/beval.o: beval.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h farsi.h arabic.h proto.h globals.h farsi.h arabic.h
objects/blob.o: blob.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h farsi.h arabic.h
objects/blowfish.o: blowfish.c vim.h protodef.h auto/config.h feature.h os_unix.h \ objects/blowfish.o: blowfish.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \

167
src/blob.c Normal file
View File

@@ -0,0 +1,167 @@
/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* blob.c: Blob support by Yasuhiro Matsumoto
*/
#include "vim.h"
#if defined(FEAT_EVAL) || defined(PROTO)
/*
* Allocate an empty blob.
* Caller should take care of the reference count.
*/
blob_T *
blob_alloc(void)
{
blob_T *blob = (blob_T *)alloc_clear(sizeof(blob_T));
if (blob != NULL)
ga_init2(&blob->bv_ga, 1, 100);
return blob;
}
/*
* Allocate an empty blob for a return value, with reference count set.
* Returns OK or FAIL.
*/
int
rettv_blob_alloc(typval_T *rettv)
{
blob_T *b = blob_alloc();
if (b == NULL)
return FAIL;
rettv_blob_set(rettv, b);
return OK;
}
/*
* Set a blob as the return value.
*/
void
rettv_blob_set(typval_T *rettv, blob_T *b)
{
rettv->v_type = VAR_BLOB;
rettv->vval.v_blob = b;
if (b != NULL)
++b->bv_refcount;
}
void
blob_free(blob_T *b)
{
ga_clear(&b->bv_ga);
vim_free(b);
}
/*
* Unreference a blob: decrement the reference count and free it when it
* becomes zero.
*/
void
blob_unref(blob_T *b)
{
if (b != NULL && --b->bv_refcount <= 0)
blob_free(b);
}
/*
* Get the length of data.
*/
long
blob_len(blob_T *b)
{
if (b == NULL)
return 0L;
return b->bv_ga.ga_len;
}
/*
* Get byte "idx" in blob "b".
* Caller must check that "idx" is valid.
*/
char_u
blob_get(blob_T *b, int idx)
{
return ((char_u*)b->bv_ga.ga_data)[idx];
}
/*
* Store one byte "c" in blob "b" at "idx".
* Caller must make sure that "idx" is valid.
*/
void
blob_set(blob_T *b, int idx, char_u c)
{
((char_u*)b->bv_ga.ga_data)[idx] = c;
}
/*
* Return TRUE when two blobs have exactly the same values.
*/
int
blob_equal(
blob_T *b1,
blob_T *b2)
{
int i;
if (b1 == NULL || b2 == NULL)
return FALSE;
if (b1 == b2)
return TRUE;
if (blob_len(b1) != blob_len(b2))
return FALSE;
for (i = 0; i < b1->bv_ga.ga_len; i++)
if (blob_get(b1, i) != blob_get(b2, i)) return FALSE;
return TRUE;
}
/*
* Read "blob" from file "fd".
* Return OK or FAIL.
*/
int
read_blob(FILE *fd, blob_T *blob)
{
struct stat st;
if (fstat(fileno(fd), &st) < 0)
return FAIL;
if (ga_grow(&blob->bv_ga, st.st_size) == FAIL)
return FAIL;
blob->bv_ga.ga_len = st.st_size;
if (fread(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd)
< (size_t)blob->bv_ga.ga_len)
return FAIL;
return OK;
}
/*
* Write "blob" to file "fd".
* Return OK or FAIL.
*/
int
write_blob(FILE *fd, blob_T *blob)
{
if (fwrite(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd)
< (size_t)blob->bv_ga.ga_len)
{
EMSG(_(e_write));
return FAIL;
}
return OK;
}
#endif /* defined(FEAT_EVAL) */

View File

@@ -1665,7 +1665,7 @@ channel_first_nl(readq_T *node)
* Returns NULL if there is nothing. * Returns NULL if there is nothing.
*/ */
char_u * char_u *
channel_get(channel_T *channel, ch_part_T part) channel_get(channel_T *channel, ch_part_T part, int *outlen)
{ {
readq_T *head = &channel->ch_part[part].ch_head; readq_T *head = &channel->ch_part[part].ch_head;
readq_T *node = head->rq_next; readq_T *node = head->rq_next;
@@ -1673,6 +1673,8 @@ channel_get(channel_T *channel, ch_part_T part)
if (node == NULL) if (node == NULL)
return NULL; return NULL;
if (outlen != NULL)
*outlen += node->rq_buflen;
/* dispose of the node but keep the buffer */ /* dispose of the node but keep the buffer */
p = node->rq_buffer; p = node->rq_buffer;
head->rq_next = node->rq_next; head->rq_next = node->rq_next;
@@ -1689,7 +1691,7 @@ channel_get(channel_T *channel, ch_part_T part)
* Replaces NUL bytes with NL. * Replaces NUL bytes with NL.
*/ */
static char_u * static char_u *
channel_get_all(channel_T *channel, ch_part_T part) channel_get_all(channel_T *channel, ch_part_T part, int *outlen)
{ {
readq_T *head = &channel->ch_part[part].ch_head; readq_T *head = &channel->ch_part[part].ch_head;
readq_T *node = head->rq_next; readq_T *node = head->rq_next;
@@ -1699,7 +1701,7 @@ channel_get_all(channel_T *channel, ch_part_T part)
/* If there is only one buffer just get that one. */ /* If there is only one buffer just get that one. */
if (head->rq_next == NULL || head->rq_next->rq_next == NULL) if (head->rq_next == NULL || head->rq_next->rq_next == NULL)
return channel_get(channel, part); return channel_get(channel, part, outlen);
/* Concatenate everything into one buffer. */ /* Concatenate everything into one buffer. */
for (node = head->rq_next; node != NULL; node = node->rq_next) for (node = head->rq_next; node != NULL; node = node->rq_next)
@@ -1718,10 +1720,16 @@ channel_get_all(channel_T *channel, ch_part_T part)
/* Free all buffers */ /* Free all buffers */
do do
{ {
p = channel_get(channel, part); p = channel_get(channel, part, NULL);
vim_free(p); vim_free(p);
} while (p != NULL); } while (p != NULL);
if (outlen != NULL)
{
*outlen += len;
return res;
}
/* turn all NUL into NL */ /* turn all NUL into NL */
while (len > 0) while (len > 0)
{ {
@@ -1893,7 +1901,7 @@ channel_fill(js_read_T *reader)
{ {
channel_T *channel = (channel_T *)reader->js_cookie; channel_T *channel = (channel_T *)reader->js_cookie;
ch_part_T part = reader->js_cookie_arg; ch_part_T part = reader->js_cookie_arg;
char_u *next = channel_get(channel, part); char_u *next = channel_get(channel, part, NULL);
int keeplen; int keeplen;
int addlen; int addlen;
char_u *p; char_u *p;
@@ -1942,7 +1950,7 @@ channel_parse_json(channel_T *channel, ch_part_T part)
if (channel_peek(channel, part) == NULL) if (channel_peek(channel, part) == NULL)
return FALSE; return FALSE;
reader.js_buf = channel_get(channel, part); reader.js_buf = channel_get(channel, part, NULL);
reader.js_used = 0; reader.js_used = 0;
reader.js_fill = channel_fill; reader.js_fill = channel_fill;
reader.js_cookie = channel; reader.js_cookie = channel;
@@ -2475,7 +2483,7 @@ drop_messages(channel_T *channel, ch_part_T part)
{ {
char_u *msg; char_u *msg;
while ((msg = channel_get(channel, part)) != NULL) while ((msg = channel_get(channel, part, NULL)) != NULL)
{ {
ch_log(channel, "Dropping message '%s'", (char *)msg); ch_log(channel, "Dropping message '%s'", (char *)msg);
vim_free(msg); vim_free(msg);
@@ -2639,7 +2647,7 @@ may_invoke_callback(channel_T *channel, ch_part_T part)
if (nl + 1 == buf + node->rq_buflen) if (nl + 1 == buf + node->rq_buflen)
{ {
/* get the whole buffer, drop the NL */ /* get the whole buffer, drop the NL */
msg = channel_get(channel, part); msg = channel_get(channel, part, NULL);
*nl = NUL; *nl = NUL;
} }
else else
@@ -2655,7 +2663,7 @@ may_invoke_callback(channel_T *channel, ch_part_T part)
/* For a raw channel we don't know where the message ends, just /* For a raw channel we don't know where the message ends, just
* get everything we have. * get everything we have.
* Convert NUL to NL, the internal representation. */ * Convert NUL to NL, the internal representation. */
msg = channel_get_all(channel, part); msg = channel_get_all(channel, part, NULL);
} }
if (msg == NULL) if (msg == NULL)
@@ -3007,7 +3015,7 @@ channel_clear_one(channel_T *channel, ch_part_T part)
cbq_T *cb_head = &ch_part->ch_cb_head; cbq_T *cb_head = &ch_part->ch_cb_head;
while (channel_peek(channel, part) != NULL) while (channel_peek(channel, part) != NULL)
vim_free(channel_get(channel, part)); vim_free(channel_get(channel, part, NULL));
while (cb_head->cq_next != NULL) while (cb_head->cq_next != NULL)
{ {
@@ -3381,7 +3389,8 @@ channel_read(channel_T *channel, ch_part_T part, char *func)
* Returns NULL in case of error or timeout. * Returns NULL in case of error or timeout.
*/ */
static char_u * static char_u *
channel_read_block(channel_T *channel, ch_part_T part, int timeout, int raw) channel_read_block(
channel_T *channel, ch_part_T part, int timeout, int raw, int *outlen)
{ {
char_u *buf; char_u *buf;
char_u *msg; char_u *msg;
@@ -3422,9 +3431,9 @@ channel_read_block(channel_T *channel, ch_part_T part, int timeout, int raw)
} }
/* We have a complete message now. */ /* We have a complete message now. */
if (mode == MODE_RAW) if (mode == MODE_RAW || outlen != NULL)
{ {
msg = channel_get_all(channel, part); msg = channel_get_all(channel, part, outlen);
} }
else else
{ {
@@ -3441,12 +3450,12 @@ channel_read_block(channel_T *channel, ch_part_T part, int timeout, int raw)
if (nl == NULL) if (nl == NULL)
{ {
/* must be a closed channel with missing NL */ /* must be a closed channel with missing NL */
msg = channel_get(channel, part); msg = channel_get(channel, part, NULL);
} }
else if (nl + 1 == buf + node->rq_buflen) else if (nl + 1 == buf + node->rq_buflen)
{ {
/* get the whole buffer */ /* get the whole buffer */
msg = channel_get(channel, part); msg = channel_get(channel, part, NULL);
*nl = NUL; *nl = NUL;
} }
else else
@@ -3554,7 +3563,7 @@ channel_read_json_block(
* Common for ch_read() and ch_readraw(). * Common for ch_read() and ch_readraw().
*/ */
void void
common_channel_read(typval_T *argvars, typval_T *rettv, int raw) common_channel_read(typval_T *argvars, typval_T *rettv, int raw, int blob)
{ {
channel_T *channel; channel_T *channel;
ch_part_T part = PART_COUNT; ch_part_T part = PART_COUNT;
@@ -3585,9 +3594,32 @@ common_channel_read(typval_T *argvars, typval_T *rettv, int raw)
if (opt.jo_set & JO_TIMEOUT) if (opt.jo_set & JO_TIMEOUT)
timeout = opt.jo_timeout; timeout = opt.jo_timeout;
if (raw || mode == MODE_RAW || mode == MODE_NL) if (blob)
{
int outlen = 0;
char_u *p = channel_read_block(channel, part,
timeout, TRUE, &outlen);
if (p != NULL)
{
blob_T *b = blob_alloc();
if (b != NULL)
{
b->bv_ga.ga_len = outlen;
if (ga_grow(&b->bv_ga, outlen) == FAIL)
blob_free(b);
else
{
memcpy(b->bv_ga.ga_data, p, outlen);
rettv_blob_set(rettv, b);
}
}
vim_free(p);
}
}
else if (raw || mode == MODE_RAW || mode == MODE_NL)
rettv->vval.v_string = channel_read_block(channel, part, rettv->vval.v_string = channel_read_block(channel, part,
timeout, raw); timeout, raw, NULL);
else else
{ {
if (opt.jo_set & JO_ID) if (opt.jo_set & JO_ID)
@@ -3905,6 +3937,7 @@ channel_send(
send_common( send_common(
typval_T *argvars, typval_T *argvars,
char_u *text, char_u *text,
int len,
int id, int id,
int eval, int eval,
jobopt_T *opt, jobopt_T *opt,
@@ -3938,7 +3971,7 @@ send_common(
opt->jo_callback, opt->jo_partial, id); opt->jo_callback, opt->jo_partial, id);
} }
if (channel_send(channel, part_send, text, (int)STRLEN(text), fun) == OK if (channel_send(channel, part_send, text, len, fun) == OK
&& opt->jo_callback == NULL) && opt->jo_callback == NULL)
return channel; return channel;
return NULL; return NULL;
@@ -3982,7 +4015,7 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval)
if (text == NULL) if (text == NULL)
return; return;
channel = send_common(argvars, text, id, eval, &opt, channel = send_common(argvars, text, (int)STRLEN(text), id, eval, &opt,
eval ? "ch_evalexpr" : "ch_sendexpr", &part_read); eval ? "ch_evalexpr" : "ch_sendexpr", &part_read);
vim_free(text); vim_free(text);
if (channel != NULL && eval) if (channel != NULL && eval)
@@ -4014,6 +4047,7 @@ ch_raw_common(typval_T *argvars, typval_T *rettv, int eval)
{ {
char_u buf[NUMBUFLEN]; char_u buf[NUMBUFLEN];
char_u *text; char_u *text;
int len;
channel_T *channel; channel_T *channel;
ch_part_T part_read; ch_part_T part_read;
jobopt_T opt; jobopt_T opt;
@@ -4023,8 +4057,17 @@ ch_raw_common(typval_T *argvars, typval_T *rettv, int eval)
rettv->v_type = VAR_STRING; rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL; rettv->vval.v_string = NULL;
text = tv_get_string_buf(&argvars[1], buf); if (argvars[1].v_type == VAR_BLOB)
channel = send_common(argvars, text, 0, eval, &opt, {
text = argvars[1].vval.v_blob->bv_ga.ga_data;
len = argvars[1].vval.v_blob->bv_ga.ga_len;
}
else
{
text = tv_get_string_buf(&argvars[1], buf);
len = STRLEN(text);
}
channel = send_common(argvars, text, len, 0, eval, &opt,
eval ? "ch_evalraw" : "ch_sendraw", &part_read); eval ? "ch_evalraw" : "ch_sendraw", &part_read);
if (channel != NULL && eval) if (channel != NULL && eval)
{ {
@@ -4033,7 +4076,7 @@ ch_raw_common(typval_T *argvars, typval_T *rettv, int eval)
else else
timeout = channel_get_timeout(channel, part_read); timeout = channel_get_timeout(channel, part_read);
rettv->vval.v_string = channel_read_block(channel, part_read, rettv->vval.v_string = channel_read_block(channel, part_read,
timeout, TRUE); timeout, TRUE, NULL);
} }
free_job_options(&opt); free_job_options(&opt);
} }

View File

@@ -78,6 +78,8 @@ typedef struct
int fi_varcount; /* nr of variables in the list */ int fi_varcount; /* nr of variables in the list */
listwatch_T fi_lw; /* keep an eye on the item used. */ listwatch_T fi_lw; /* keep an eye on the item used. */
list_T *fi_list; /* list being used */ list_T *fi_list; /* list being used */
int fi_bi; /* index of blob */
blob_T *fi_blob; /* blob being used */
} forinfo_T; } forinfo_T;
@@ -187,6 +189,7 @@ static struct vimvar
{VV_NAME("t_none", VAR_NUMBER), VV_RO}, {VV_NAME("t_none", VAR_NUMBER), VV_RO},
{VV_NAME("t_job", VAR_NUMBER), VV_RO}, {VV_NAME("t_job", VAR_NUMBER), VV_RO},
{VV_NAME("t_channel", VAR_NUMBER), VV_RO}, {VV_NAME("t_channel", VAR_NUMBER), VV_RO},
{VV_NAME("t_blob", VAR_NUMBER), VV_RO},
{VV_NAME("termrfgresp", VAR_STRING), VV_RO}, {VV_NAME("termrfgresp", VAR_STRING), VV_RO},
{VV_NAME("termrbgresp", VAR_STRING), VV_RO}, {VV_NAME("termrbgresp", VAR_STRING), VV_RO},
{VV_NAME("termu7resp", VAR_STRING), VV_RO}, {VV_NAME("termu7resp", VAR_STRING), VV_RO},
@@ -202,6 +205,7 @@ static struct vimvar
#define vv_str vv_di.di_tv.vval.v_string #define vv_str vv_di.di_tv.vval.v_string
#define vv_list vv_di.di_tv.vval.v_list #define vv_list vv_di.di_tv.vval.v_list
#define vv_dict vv_di.di_tv.vval.v_dict #define vv_dict vv_di.di_tv.vval.v_dict
#define vv_blob vv_di.di_tv.vval.v_blob
#define vv_tv vv_di.di_tv #define vv_tv vv_di.di_tv
static dictitem_T vimvars_var; /* variable used for v: */ static dictitem_T vimvars_var; /* variable used for v: */
@@ -338,6 +342,7 @@ eval_init(void)
set_vim_var_nr(VV_TYPE_NONE, VAR_TYPE_NONE); set_vim_var_nr(VV_TYPE_NONE, VAR_TYPE_NONE);
set_vim_var_nr(VV_TYPE_JOB, VAR_TYPE_JOB); set_vim_var_nr(VV_TYPE_JOB, VAR_TYPE_JOB);
set_vim_var_nr(VV_TYPE_CHANNEL, VAR_TYPE_CHANNEL); set_vim_var_nr(VV_TYPE_CHANNEL, VAR_TYPE_CHANNEL);
set_vim_var_nr(VV_TYPE_BLOB, VAR_TYPE_BLOB);
set_reg_var(0); /* default for v:register is not 0 but '"' */ set_reg_var(0); /* default for v:register is not 0 but '"' */
@@ -1918,10 +1923,12 @@ get_lval(
{ {
if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL) if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL)
&& !(lp->ll_tv->v_type == VAR_DICT && !(lp->ll_tv->v_type == VAR_DICT
&& lp->ll_tv->vval.v_dict != NULL)) && lp->ll_tv->vval.v_dict != NULL)
&& !(lp->ll_tv->v_type == VAR_BLOB
&& lp->ll_tv->vval.v_blob != NULL))
{ {
if (!quiet) if (!quiet)
EMSG(_("E689: Can only index a List or Dictionary")); EMSG(_("E689: Can only index a List, Dictionary or Blob"));
return NULL; return NULL;
} }
if (lp->ll_range) if (lp->ll_range)
@@ -1974,11 +1981,14 @@ get_lval(
clear_tv(&var1); clear_tv(&var1);
return NULL; return NULL;
} }
if (rettv != NULL && (rettv->v_type != VAR_LIST if (rettv != NULL
|| rettv->vval.v_list == NULL)) && !(rettv->v_type == VAR_LIST
|| rettv->vval.v_list != NULL)
&& !(rettv->v_type == VAR_BLOB
|| rettv->vval.v_blob != NULL))
{ {
if (!quiet) if (!quiet)
EMSG(_("E709: [:] requires a List value")); EMSG(_("E709: [:] requires a List or Blob value"));
clear_tv(&var1); clear_tv(&var1);
return NULL; return NULL;
} }
@@ -2097,6 +2107,33 @@ get_lval(
clear_tv(&var1); clear_tv(&var1);
lp->ll_tv = &lp->ll_di->di_tv; lp->ll_tv = &lp->ll_di->di_tv;
} }
else if (lp->ll_tv->v_type == VAR_BLOB)
{
/*
* Get the number and item for the only or first index of the List.
*/
if (empty1)
lp->ll_n1 = 0;
else
// is number or string
lp->ll_n1 = (long)tv_get_number(&var1);
clear_tv(&var1);
if (lp->ll_n1 < 0
|| lp->ll_n1 > blob_len(lp->ll_tv->vval.v_blob))
{
if (!quiet)
EMSGN(_(e_listidx), lp->ll_n1);
return NULL;
}
if (lp->ll_range && !lp->ll_empty2)
{
lp->ll_n2 = (long)tv_get_number(&var2);
clear_tv(&var2);
}
lp->ll_blob = lp->ll_tv->vval.v_blob;
lp->ll_tv = NULL;
}
else else
{ {
/* /*
@@ -2201,7 +2238,52 @@ set_var_lval(
{ {
cc = *endp; cc = *endp;
*endp = NUL; *endp = NUL;
if (op != NULL && *op != '=') if (lp->ll_blob != NULL)
{
int error = FALSE, val;
if (op != NULL && *op != '=')
{
EMSG2(_(e_letwrong), op);
return;
}
if (lp->ll_range && rettv->v_type == VAR_BLOB)
{
int i;
if (blob_len(rettv->vval.v_blob) != blob_len(lp->ll_blob))
{
EMSG(_("E972: Blob value has more items than target"));
return;
}
for (i = lp->ll_n1; i <= lp->ll_n2; i++)
blob_set(lp->ll_blob, i,
blob_get(rettv->vval.v_blob, i));
}
else
{
val = (int)tv_get_number_chk(rettv, &error);
if (!error)
{
garray_T *gap = &lp->ll_blob->bv_ga;
// Allow for appending a byte. Setting a byte beyond
// the end is an error otherwise.
if (lp->ll_n1 < gap->ga_len
|| (lp->ll_n1 == gap->ga_len
&& ga_grow(&lp->ll_blob->bv_ga, 1) == OK))
{
blob_set(lp->ll_blob, lp->ll_n1, val);
if (lp->ll_n1 == gap->ga_len)
++gap->ga_len;
}
else
EMSG(_(e_invrange));
}
}
}
else if (op != NULL && *op != '=')
{ {
typval_T tv; typval_T tv;
@@ -2352,6 +2434,20 @@ tv_op(typval_T *tv1, typval_T *tv2, char_u *op)
case VAR_CHANNEL: case VAR_CHANNEL:
break; break;
case VAR_BLOB:
if (*op != '+' || tv2->v_type != VAR_BLOB)
break;
// BLOB += BLOB
if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL)
{
blob_T *b1 = tv1->vval.v_blob;
blob_T *b2 = tv2->vval.v_blob;
int i, len = blob_len(b2);
for (i = 0; i < len; i++)
ga_append(&b1->bv_ga, blob_get(b2, i));
}
return OK;
case VAR_LIST: case VAR_LIST:
if (*op != '+' || tv2->v_type != VAR_LIST) if (*op != '+' || tv2->v_type != VAR_LIST)
break; break;
@@ -2451,6 +2547,7 @@ eval_for_line(
char_u *expr; char_u *expr;
typval_T tv; typval_T tv;
list_T *l; list_T *l;
blob_T *b;
*errp = TRUE; /* default: there is an error */ *errp = TRUE; /* default: there is an error */
@@ -2476,24 +2573,38 @@ eval_for_line(
*errp = FALSE; *errp = FALSE;
if (!skip) if (!skip)
{ {
l = tv.vval.v_list; if (tv.v_type == VAR_LIST)
if (tv.v_type != VAR_LIST)
{ {
EMSG(_(e_listreq)); l = tv.vval.v_list;
clear_tv(&tv); if (l == NULL)
{
// a null list is like an empty list: do nothing
clear_tv(&tv);
}
else
{
// No need to increment the refcount, it's already set for
// the list being used in "tv".
fi->fi_list = l;
list_add_watch(l, &fi->fi_lw);
fi->fi_lw.lw_item = l->lv_first;
}
} }
else if (l == NULL) else if (tv.v_type == VAR_BLOB)
{ {
/* a null list is like an empty list: do nothing */ b = tv.vval.v_blob;
clear_tv(&tv); if (b == NULL)
clear_tv(&tv);
else
{
fi->fi_blob = b;
fi->fi_bi = 0;
}
} }
else else
{ {
/* No need to increment the refcount, it's already set for the EMSG(_(e_listreq));
* list being used in "tv". */ clear_tv(&tv);
fi->fi_list = l;
list_add_watch(l, &fi->fi_lw);
fi->fi_lw.lw_item = l->lv_first;
} }
} }
} }
@@ -2516,6 +2627,20 @@ next_for_item(void *fi_void, char_u *arg)
int result; int result;
listitem_T *item; listitem_T *item;
if (fi->fi_blob != NULL)
{
typval_T tv;
if (fi->fi_bi >= blob_len(fi->fi_blob))
return FALSE;
tv.v_type = VAR_NUMBER;
tv.v_lock = VAR_FIXED;
tv.vval.v_number = blob_get(fi->fi_blob, fi->fi_bi);
++fi->fi_bi;
return ex_let_vars(arg, &tv, TRUE,
fi->fi_semicolon, fi->fi_varcount, NULL) == OK;
}
item = fi->fi_lw.lw_item; item = fi->fi_lw.lw_item;
if (item == NULL) if (item == NULL)
result = FALSE; result = FALSE;
@@ -2955,6 +3080,7 @@ item_lock(typval_T *tv, int deep, int lock)
list_T *l; list_T *l;
listitem_T *li; listitem_T *li;
dict_T *d; dict_T *d;
blob_T *b;
hashitem_T *hi; hashitem_T *hi;
int todo; int todo;
@@ -2986,6 +3112,15 @@ item_lock(typval_T *tv, int deep, int lock)
case VAR_CHANNEL: case VAR_CHANNEL:
break; break;
case VAR_BLOB:
if ((b = tv->vval.v_blob) != NULL)
{
if (lock)
b->bv_lock |= VAR_LOCKED;
else
b->bv_lock &= ~VAR_LOCKED;
}
break;
case VAR_LIST: case VAR_LIST:
if ((l = tv->vval.v_list) != NULL) if ((l = tv->vval.v_list) != NULL)
{ {
@@ -3609,7 +3744,8 @@ eval5(char_u **arg, typval_T *rettv, int evaluate)
if (op != '+' && op != '-' && op != '.') if (op != '+' && op != '-' && op != '.')
break; break;
if ((op != '+' || rettv->v_type != VAR_LIST) if ((op != '+' || (rettv->v_type != VAR_LIST
&& rettv->v_type != VAR_BLOB))
#ifdef FEAT_FLOAT #ifdef FEAT_FLOAT
&& (op == '.' || rettv->v_type != VAR_FLOAT) && (op == '.' || rettv->v_type != VAR_FLOAT)
#endif #endif
@@ -3659,6 +3795,25 @@ eval5(char_u **arg, typval_T *rettv, int evaluate)
rettv->v_type = VAR_STRING; rettv->v_type = VAR_STRING;
rettv->vval.v_string = p; rettv->vval.v_string = p;
} }
else if (op == '+' && rettv->v_type == VAR_BLOB
&& var2.v_type == VAR_BLOB)
{
blob_T *b1 = rettv->vval.v_blob;
blob_T *b2 = var2.vval.v_blob;
blob_T *b = blob_alloc();
int i;
if (b != NULL)
{
for (i = 0; i < blob_len(b1); i++)
ga_append(&b->bv_ga, blob_get(b1, i));
for (i = 0; i < blob_len(b2); i++)
ga_append(&b->bv_ga, blob_get(b2, i));
clear_tv(rettv);
rettv_blob_set(rettv, b);
}
}
else if (op == '+' && rettv->v_type == VAR_LIST else if (op == '+' && rettv->v_type == VAR_LIST
&& var2.v_type == VAR_LIST) && var2.v_type == VAR_LIST)
{ {
@@ -3921,6 +4076,7 @@ eval6(
/* /*
* Handle sixth level expression: * Handle sixth level expression:
* number number constant * number number constant
* 0zFFFFFFFF Blob constant
* "string" string constant * "string" string constant
* 'string' literal string constant * 'string' literal string constant
* &option-name option value * &option-name option value
@@ -4027,7 +4183,38 @@ eval7(
} }
else else
#endif #endif
if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z'))
{ {
char_u *bp;
blob_T *blob;
// Blob constant: 0z0123456789abcdef
if (evaluate)
blob = blob_alloc();
for (bp = *arg + 2; vim_isxdigit(bp[0]); bp += 2)
{
if (!vim_isxdigit(bp[1]))
{
EMSG(_("E973: Blob literal should have an even number of hex characters'"));
vim_free(blob);
ret = FAIL;
break;
}
if (blob != NULL)
ga_append(&blob->bv_ga,
(hex2nr(*bp) << 4) + hex2nr(*(bp+1)));
}
if (blob != NULL)
{
++blob->bv_refcount;
rettv->v_type = VAR_BLOB;
rettv->vval.v_blob = blob;
}
*arg = bp;
}
else
{
// decimal, hex or octal number
vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0); vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0);
*arg += len; *arg += len;
if (evaluate) if (evaluate)
@@ -4263,6 +4450,7 @@ eval_index(
{ {
int empty1 = FALSE, empty2 = FALSE; int empty1 = FALSE, empty2 = FALSE;
typval_T var1, var2; typval_T var1, var2;
long i;
long n1, n2 = 0; long n1, n2 = 0;
long len = -1; long len = -1;
int range = FALSE; int range = FALSE;
@@ -4297,6 +4485,7 @@ eval_index(
case VAR_NUMBER: case VAR_NUMBER:
case VAR_LIST: case VAR_LIST:
case VAR_DICT: case VAR_DICT:
case VAR_BLOB:
break; break;
} }
@@ -4439,6 +4628,67 @@ eval_index(
rettv->vval.v_string = s; rettv->vval.v_string = s;
break; break;
case VAR_BLOB:
len = blob_len(rettv->vval.v_blob);
if (range)
{
// The resulting variable is a substring. If the indexes
// are out of range the result is empty.
if (n1 < 0)
{
n1 = len + n1;
if (n1 < 0)
n1 = 0;
}
if (n2 < 0)
n2 = len + n2;
else if (n2 >= len)
n2 = len - 1;
if (n1 >= len || n2 < 0 || n1 > n2)
{
clear_tv(rettv);
rettv->v_type = VAR_BLOB;
rettv->vval.v_blob = NULL;
}
else
{
blob_T *blob = blob_alloc();
if (blob != NULL)
{
if (ga_grow(&blob->bv_ga, n2 - n1 + 1) == FAIL)
{
blob_free(blob);
return FAIL;
}
blob->bv_ga.ga_len = n2 - n1 + 1;
for (i = n1; i <= n2; i++)
blob_set(blob, i - n1,
blob_get(rettv->vval.v_blob, i));
clear_tv(rettv);
rettv_blob_set(rettv, blob);
}
}
}
else
{
// The resulting variable is a string of a single
// character. If the index is too big or negative the
// result is empty.
if (n1 < len && n1 >= 0)
{
int v = (int)blob_get(rettv->vval.v_blob, n1);
clear_tv(rettv);
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = v;
}
else
EMSGN(_(e_blobidx), n1);
}
break;
case VAR_LIST: case VAR_LIST:
len = list_len(rettv->vval.v_list); len = list_len(rettv->vval.v_list);
if (n1 < 0) if (n1 < 0)
@@ -4970,6 +5220,9 @@ tv_equal(
--recursive_cnt; --recursive_cnt;
return r; return r;
case VAR_BLOB:
return blob_equal(tv1->vval.v_blob, tv2->vval.v_blob);
case VAR_NUMBER: case VAR_NUMBER:
return tv1->vval.v_number == tv2->vval.v_number; return tv1->vval.v_number == tv2->vval.v_number;
@@ -5602,6 +5855,36 @@ echo_string_core(
break; break;
} }
case VAR_BLOB:
if (tv->vval.v_blob == NULL)
{
*tofree = NULL;
r = (char_u *)"[]";
}
else
{
blob_T *b;
int i;
garray_T ga;
// Store bytes in the growarray.
ga_init2(&ga, 1, 4000);
b = tv->vval.v_blob;
ga_append(&ga, '[');
for (i = 0; i < blob_len(b); i++)
{
if (i > 0)
ga_concat(&ga, (char_u *)",");
vim_snprintf((char *)numbuf, NUMBUFLEN, "0x%02X",
(int)blob_get(b, i));
ga_concat(&ga, numbuf);
}
ga_append(&ga, ']');
*tofree = ga.ga_data;
r = *tofree;
}
break;
case VAR_LIST: case VAR_LIST:
if (tv->vval.v_list == NULL) if (tv->vval.v_list == NULL)
{ {
@@ -6841,6 +7124,9 @@ free_tv(typval_T *varp)
case VAR_PARTIAL: case VAR_PARTIAL:
partial_unref(varp->vval.v_partial); partial_unref(varp->vval.v_partial);
break; break;
case VAR_BLOB:
blob_unref(varp->vval.v_blob);
break;
case VAR_LIST: case VAR_LIST:
list_unref(varp->vval.v_list); list_unref(varp->vval.v_list);
break; break;
@@ -6887,6 +7173,10 @@ clear_tv(typval_T *varp)
partial_unref(varp->vval.v_partial); partial_unref(varp->vval.v_partial);
varp->vval.v_partial = NULL; varp->vval.v_partial = NULL;
break; break;
case VAR_BLOB:
blob_unref(varp->vval.v_blob);
varp->vval.v_blob = NULL;
break;
case VAR_LIST: case VAR_LIST:
list_unref(varp->vval.v_list); list_unref(varp->vval.v_list);
varp->vval.v_list = NULL; varp->vval.v_list = NULL;
@@ -6990,6 +7280,9 @@ tv_get_number_chk(typval_T *varp, int *denote)
EMSG(_("E913: Using a Channel as a Number")); EMSG(_("E913: Using a Channel as a Number"));
break; break;
#endif #endif
case VAR_BLOB:
EMSG(_("E974: Using a Blob as a Number"));
break;
case VAR_UNKNOWN: case VAR_UNKNOWN:
internal_error("tv_get_number(UNKNOWN)"); internal_error("tv_get_number(UNKNOWN)");
break; break;
@@ -7037,6 +7330,9 @@ tv_get_float(typval_T *varp)
EMSG(_("E914: Using a Channel as a Float")); EMSG(_("E914: Using a Channel as a Float"));
break; break;
# endif # endif
case VAR_BLOB:
EMSG(_("E975: Using a Blob as a Float"));
break;
case VAR_UNKNOWN: case VAR_UNKNOWN:
internal_error("tv_get_float(UNKNOWN)"); internal_error("tv_get_float(UNKNOWN)");
break; break;
@@ -7113,6 +7409,9 @@ tv_get_string_buf_chk(typval_T *varp, char_u *buf)
case VAR_SPECIAL: case VAR_SPECIAL:
STRCPY(buf, get_var_special_name(varp->vval.v_number)); STRCPY(buf, get_var_special_name(varp->vval.v_number));
return buf; return buf;
case VAR_BLOB:
EMSG(_("E976: using Blob as a String"));
break;
case VAR_JOB: case VAR_JOB:
#ifdef FEAT_JOB_CHANNEL #ifdef FEAT_JOB_CHANNEL
{ {
@@ -7805,6 +8104,15 @@ copy_tv(typval_T *from, typval_T *to)
++to->vval.v_partial->pt_refcount; ++to->vval.v_partial->pt_refcount;
} }
break; break;
case VAR_BLOB:
if (from->vval.v_blob == NULL)
to->vval.v_blob = NULL;
else
{
to->vval.v_blob = from->vval.v_blob;
++to->vval.v_blob->bv_refcount;
}
break;
case VAR_LIST: case VAR_LIST:
if (from->vval.v_list == NULL) if (from->vval.v_list == NULL)
to->vval.v_list = NULL; to->vval.v_list = NULL;
@@ -7863,6 +8171,7 @@ item_copy(
case VAR_SPECIAL: case VAR_SPECIAL:
case VAR_JOB: case VAR_JOB:
case VAR_CHANNEL: case VAR_CHANNEL:
case VAR_BLOB:
copy_tv(from, to); copy_tv(from, to);
break; break;
case VAR_LIST: case VAR_LIST:
@@ -8601,6 +8910,7 @@ read_viminfo_varlist(vir_T *virp, int writing)
#endif #endif
case 'D': type = VAR_DICT; break; case 'D': type = VAR_DICT; break;
case 'L': type = VAR_LIST; break; case 'L': type = VAR_LIST; break;
case 'B': type = VAR_BLOB; break;
case 'X': type = VAR_SPECIAL; break; case 'X': type = VAR_SPECIAL; break;
} }
@@ -8608,7 +8918,8 @@ read_viminfo_varlist(vir_T *virp, int writing)
if (tab != NULL) if (tab != NULL)
{ {
tv.v_type = type; tv.v_type = type;
if (type == VAR_STRING || type == VAR_DICT || type == VAR_LIST) if (type == VAR_STRING || type == VAR_DICT ||
type == VAR_LIST || type == VAR_BLOB)
tv.vval.v_string = viminfo_readstring(virp, tv.vval.v_string = viminfo_readstring(virp,
(int)(tab - virp->vir_line + 1), TRUE); (int)(tab - virp->vir_line + 1), TRUE);
#ifdef FEAT_FLOAT #ifdef FEAT_FLOAT
@@ -8617,7 +8928,7 @@ read_viminfo_varlist(vir_T *virp, int writing)
#endif #endif
else else
tv.vval.v_number = atol((char *)tab + 1); tv.vval.v_number = atol((char *)tab + 1);
if (type == VAR_DICT || type == VAR_LIST) if (type == VAR_DICT || type == VAR_LIST || type == VAR_BLOB)
{ {
typval_T *etv = eval_expr(tv.vval.v_string, NULL); typval_T *etv = eval_expr(tv.vval.v_string, NULL);
@@ -8640,7 +8951,8 @@ read_viminfo_varlist(vir_T *virp, int writing)
if (tv.v_type == VAR_STRING) if (tv.v_type == VAR_STRING)
vim_free(tv.vval.v_string); vim_free(tv.vval.v_string);
else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST) else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST ||
tv.v_type == VAR_BLOB)
clear_tv(&tv); clear_tv(&tv);
} }
} }
@@ -8684,6 +8996,7 @@ write_viminfo_varlist(FILE *fp)
case VAR_FLOAT: s = "FLO"; break; case VAR_FLOAT: s = "FLO"; break;
case VAR_DICT: s = "DIC"; break; case VAR_DICT: s = "DIC"; break;
case VAR_LIST: s = "LIS"; break; case VAR_LIST: s = "LIS"; break;
case VAR_BLOB: s = "BLO"; break;
case VAR_SPECIAL: s = "XPL"; break; case VAR_SPECIAL: s = "XPL"; break;
case VAR_UNKNOWN: case VAR_UNKNOWN:
@@ -9250,6 +9563,33 @@ typval_compare(
* it means TRUE. */ * it means TRUE. */
n1 = (type == TYPE_NEQUAL); n1 = (type == TYPE_NEQUAL);
} }
else if (typ1->v_type == VAR_BLOB || typ2->v_type == VAR_BLOB)
{
if (type_is)
{
n1 = (typ1->v_type == typ2->v_type
&& typ1->vval.v_blob == typ2->vval.v_blob);
if (type == TYPE_NEQUAL)
n1 = !n1;
}
else if (typ1->v_type != typ2->v_type
|| (type != TYPE_EQUAL && type != TYPE_NEQUAL))
{
if (typ1->v_type != typ2->v_type)
EMSG(_("E977: Can only compare Blob with Blob"));
else
EMSG(_(e_invalblob));
clear_tv(typ1);
return FAIL;
}
else
{
// Compare two Blobs for being equal or unequal.
n1 = blob_equal(typ1->vval.v_blob, typ2->vval.v_blob);
if (type == TYPE_NEQUAL)
n1 = !n1;
}
}
else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST) else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST)
{ {
if (type_is) if (type_is)
@@ -10278,6 +10618,7 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
dict_T *d = NULL; dict_T *d = NULL;
typval_T save_val; typval_T save_val;
typval_T save_key; typval_T save_key;
blob_T *b = NULL;
int rem; int rem;
int todo; int todo;
char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); char_u *ermsg = (char_u *)(map ? "map()" : "filter()");
@@ -10286,7 +10627,12 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
int save_did_emsg; int save_did_emsg;
int idx = 0; int idx = 0;
if (argvars[0].v_type == VAR_LIST) if (argvars[0].v_type == VAR_BLOB)
{
if ((b = argvars[0].vval.v_blob) == NULL)
return;
}
else if (argvars[0].v_type == VAR_LIST)
{ {
if ((l = argvars[0].vval.v_list) == NULL if ((l = argvars[0].vval.v_list) == NULL
|| (!map && tv_check_lock(l->lv_lock, arg_errmsg, TRUE))) || (!map && tv_check_lock(l->lv_lock, arg_errmsg, TRUE)))
@@ -10353,6 +10699,37 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
} }
hash_unlock(ht); hash_unlock(ht);
} }
else if (argvars[0].v_type == VAR_BLOB)
{
int i;
typval_T tv;
vimvars[VV_KEY].vv_type = VAR_NUMBER;
for (i = 0; i < b->bv_ga.ga_len; i++)
{
tv.v_type = VAR_NUMBER;
tv.vval.v_number = blob_get(b, i);
vimvars[VV_KEY].vv_nr = idx;
if (filter_map_one(&tv, expr, map, &rem) == FAIL || did_emsg)
break;
if (tv.v_type != VAR_NUMBER)
{
EMSG(_(e_invalblob));
return;
}
tv.v_type = VAR_NUMBER;
blob_set(b, i, tv.vval.v_number);
if (!map && rem)
{
char_u *p = (char_u *)argvars[0].vval.v_blob->bv_ga.ga_data;
mch_memmove(p + idx, p + i + 1,
(size_t)b->bv_ga.ga_len - i - 1);
--b->bv_ga.ga_len;
--i;
}
}
}
else else
{ {
vimvars[VV_KEY].vv_type = VAR_NUMBER; vimvars[VV_KEY].vv_type = VAR_NUMBER;

View File

@@ -96,6 +96,7 @@ static void f_ch_log(typval_T *argvars, typval_T *rettv);
static void f_ch_logfile(typval_T *argvars, typval_T *rettv); static void f_ch_logfile(typval_T *argvars, typval_T *rettv);
static void f_ch_open(typval_T *argvars, typval_T *rettv); static void f_ch_open(typval_T *argvars, typval_T *rettv);
static void f_ch_read(typval_T *argvars, typval_T *rettv); static void f_ch_read(typval_T *argvars, typval_T *rettv);
static void f_ch_readblob(typval_T *argvars, typval_T *rettv);
static void f_ch_readraw(typval_T *argvars, typval_T *rettv); static void f_ch_readraw(typval_T *argvars, typval_T *rettv);
static void f_ch_sendexpr(typval_T *argvars, typval_T *rettv); static void f_ch_sendexpr(typval_T *argvars, typval_T *rettv);
static void f_ch_sendraw(typval_T *argvars, typval_T *rettv); static void f_ch_sendraw(typval_T *argvars, typval_T *rettv);
@@ -570,6 +571,7 @@ static struct fst
{"ch_logfile", 1, 2, f_ch_logfile}, {"ch_logfile", 1, 2, f_ch_logfile},
{"ch_open", 1, 2, f_ch_open}, {"ch_open", 1, 2, f_ch_open},
{"ch_read", 1, 2, f_ch_read}, {"ch_read", 1, 2, f_ch_read},
{"ch_readblob", 1, 2, f_ch_readblob},
{"ch_readraw", 1, 2, f_ch_readraw}, {"ch_readraw", 1, 2, f_ch_readraw},
{"ch_sendexpr", 2, 3, f_ch_sendexpr}, {"ch_sendexpr", 2, 3, f_ch_sendexpr},
{"ch_sendraw", 2, 3, f_ch_sendraw}, {"ch_sendraw", 2, 3, f_ch_sendraw},
@@ -1237,6 +1239,7 @@ f_acos(typval_T *argvars, typval_T *rettv)
f_add(typval_T *argvars, typval_T *rettv) f_add(typval_T *argvars, typval_T *rettv)
{ {
list_T *l; list_T *l;
blob_T *b;
rettv->vval.v_number = 1; /* Default: Failed */ rettv->vval.v_number = 1; /* Default: Failed */
if (argvars[0].v_type == VAR_LIST) if (argvars[0].v_type == VAR_LIST)
@@ -1247,6 +1250,16 @@ f_add(typval_T *argvars, typval_T *rettv)
&& list_append_tv(l, &argvars[1]) == OK) && list_append_tv(l, &argvars[1]) == OK)
copy_tv(&argvars[0], rettv); copy_tv(&argvars[0], rettv);
} }
else if (argvars[0].v_type == VAR_BLOB)
{
if ((b = argvars[0].vval.v_blob) != NULL
&& !tv_check_lock(b->bv_lock,
(char_u *)N_("add() argument"), TRUE))
{
ga_append(&b->bv_ga, (char_u)tv_get_number(&argvars[1]));
copy_tv(&argvars[0], rettv);
}
}
else else
EMSG(_(e_listreq)); EMSG(_(e_listreq));
} }
@@ -2309,7 +2322,16 @@ f_ch_open(typval_T *argvars, typval_T *rettv)
static void static void
f_ch_read(typval_T *argvars, typval_T *rettv) f_ch_read(typval_T *argvars, typval_T *rettv)
{ {
common_channel_read(argvars, rettv, FALSE); common_channel_read(argvars, rettv, FALSE, FALSE);
}
/*
* "ch_readblob()" function
*/
static void
f_ch_readblob(typval_T *argvars, typval_T *rettv)
{
common_channel_read(argvars, rettv, TRUE, TRUE);
} }
/* /*
@@ -2318,7 +2340,7 @@ f_ch_read(typval_T *argvars, typval_T *rettv)
static void static void
f_ch_readraw(typval_T *argvars, typval_T *rettv) f_ch_readraw(typval_T *argvars, typval_T *rettv)
{ {
common_channel_read(argvars, rettv, TRUE); common_channel_read(argvars, rettv, TRUE, FALSE);
} }
/* /*
@@ -3170,6 +3192,12 @@ f_empty(typval_T *argvars, typval_T *rettv)
n = argvars[0].vval.v_number != VVAL_TRUE; n = argvars[0].vval.v_number != VVAL_TRUE;
break; break;
case VAR_BLOB:
n = argvars[0].vval.v_blob == NULL
|| argvars[0].vval.v_blob->bv_ga.ga_data == NULL
|| argvars[0].vval.v_blob->bv_ga.ga_len == 0;
break;
case VAR_JOB: case VAR_JOB:
#ifdef FEAT_JOB_CHANNEL #ifdef FEAT_JOB_CHANNEL
n = argvars[0].vval.v_job == NULL n = argvars[0].vval.v_job == NULL
@@ -4365,7 +4393,21 @@ f_get(typval_T *argvars, typval_T *rettv)
dict_T *d; dict_T *d;
typval_T *tv = NULL; typval_T *tv = NULL;
if (argvars[0].v_type == VAR_LIST) if (argvars[0].v_type == VAR_BLOB)
{
int error = FALSE;
int idx = tv_get_number_chk(&argvars[1], &error);
if (!error)
{
rettv->v_type = VAR_NUMBER;
if (idx >= blob_len(argvars[0].vval.v_blob))
EMSGN(_(e_blobidx), idx);
else
rettv->vval.v_number = blob_get(argvars[0].vval.v_blob, idx);
}
}
else if (argvars[0].v_type == VAR_LIST)
{ {
if ((l = argvars[0].vval.v_list) != NULL) if ((l = argvars[0].vval.v_list) != NULL)
{ {
@@ -6965,23 +7007,50 @@ f_index(typval_T *argvars, typval_T *rettv)
{ {
list_T *l; list_T *l;
listitem_T *item; listitem_T *item;
blob_T *b;
long idx = 0; long idx = 0;
int ic = FALSE; int ic = FALSE;
int error = FALSE;
rettv->vval.v_number = -1; rettv->vval.v_number = -1;
if (argvars[0].v_type != VAR_LIST) if (argvars[0].v_type == VAR_BLOB)
{
typval_T tv;
int start = 0;
if (argvars[2].v_type != VAR_UNKNOWN)
{
start = tv_get_number_chk(&argvars[2], &error);
if (error)
return;
}
b = argvars[0].vval.v_blob;
if (b == NULL)
return;
for (idx = start; idx < blob_len(b); ++idx)
{
tv.v_type = VAR_NUMBER;
tv.vval.v_number = blob_get(b, idx);
if (tv_equal(&tv, &argvars[1], ic, FALSE))
{
rettv->vval.v_number = idx;
return;
}
}
return;
}
else if (argvars[0].v_type != VAR_LIST)
{ {
EMSG(_(e_listreq)); EMSG(_(e_listreq));
return; return;
} }
l = argvars[0].vval.v_list; l = argvars[0].vval.v_list;
if (l != NULL) if (l != NULL)
{ {
item = l->lv_first; item = l->lv_first;
if (argvars[2].v_type != VAR_UNKNOWN) if (argvars[2].v_type != VAR_UNKNOWN)
{ {
int error = FALSE;
/* Start at specified item. Use the cached index that list_find() /* Start at specified item. Use the cached index that list_find()
* sets, so that a negative number also works. */ * sets, so that a negative number also works. */
item = list_find(l, (long)tv_get_number_chk(&argvars[2], &error)); item = list_find(l, (long)tv_get_number_chk(&argvars[2], &error));
@@ -7160,10 +7229,45 @@ f_insert(typval_T *argvars, typval_T *rettv)
list_T *l; list_T *l;
int error = FALSE; int error = FALSE;
if (argvars[0].v_type != VAR_LIST) if (argvars[0].v_type == VAR_BLOB)
{
int val, len;
char_u *p;
len = blob_len(argvars[0].vval.v_blob);
if (argvars[2].v_type != VAR_UNKNOWN)
{
before = (long)tv_get_number_chk(&argvars[2], &error);
if (error)
return; // type error; errmsg already given
if (before < 0 || before > len)
{
EMSG2(_(e_invarg2), tv_get_string(&argvars[2]));
return;
}
}
val = tv_get_number_chk(&argvars[1], &error);
if (error)
return;
if (val < 0 || val > 255)
{
EMSG2(_(e_invarg2), tv_get_string(&argvars[1]));
return;
}
if (ga_grow(&argvars[0].vval.v_blob->bv_ga, 1) == FAIL)
return;
p = (char_u *)argvars[0].vval.v_blob->bv_ga.ga_data;
mch_memmove(p + before + 1, p + before, (size_t)len - before);
*(p + before) = val;
++argvars[0].vval.v_blob->bv_ga.ga_len;
copy_tv(&argvars[0], rettv);
}
else if (argvars[0].v_type != VAR_LIST)
EMSG2(_(e_listarg), "insert()"); EMSG2(_(e_listarg), "insert()");
else if ((l = argvars[0].vval.v_list) != NULL else if ((l = argvars[0].vval.v_list) != NULL && !tv_check_lock(l->lv_lock,
&& !tv_check_lock(l->lv_lock, (char_u *)N_("insert() argument"), TRUE)) (char_u *)N_("insert() argument"), TRUE))
{ {
if (argvars[2].v_type != VAR_UNKNOWN) if (argvars[2].v_type != VAR_UNKNOWN)
before = (long)tv_get_number_chk(&argvars[2], &error); before = (long)tv_get_number_chk(&argvars[2], &error);
@@ -7527,6 +7631,9 @@ f_len(typval_T *argvars, typval_T *rettv)
rettv->vval.v_number = (varnumber_T)STRLEN( rettv->vval.v_number = (varnumber_T)STRLEN(
tv_get_string(&argvars[0])); tv_get_string(&argvars[0]));
break; break;
case VAR_BLOB:
rettv->vval.v_number = blob_len(argvars[0].vval.v_blob);
break;
case VAR_LIST: case VAR_LIST:
rettv->vval.v_number = list_len(argvars[0].vval.v_list); rettv->vval.v_number = list_len(argvars[0].vval.v_list);
break; break;
@@ -8926,6 +9033,7 @@ f_range(typval_T *argvars, typval_T *rettv)
f_readfile(typval_T *argvars, typval_T *rettv) f_readfile(typval_T *argvars, typval_T *rettv)
{ {
int binary = FALSE; int binary = FALSE;
int blob = FALSE;
int failed = FALSE; int failed = FALSE;
char_u *fname; char_u *fname;
FILE *fd; FILE *fd;
@@ -8944,12 +9052,23 @@ f_readfile(typval_T *argvars, typval_T *rettv)
{ {
if (STRCMP(tv_get_string(&argvars[1]), "b") == 0) if (STRCMP(tv_get_string(&argvars[1]), "b") == 0)
binary = TRUE; binary = TRUE;
if (STRCMP(tv_get_string(&argvars[1]), "B") == 0)
blob = TRUE;
if (argvars[2].v_type != VAR_UNKNOWN) if (argvars[2].v_type != VAR_UNKNOWN)
maxline = (long)tv_get_number(&argvars[2]); maxline = (long)tv_get_number(&argvars[2]);
} }
if (rettv_list_alloc(rettv) == FAIL) if (blob)
return; {
if (rettv_blob_alloc(rettv) == FAIL)
return;
}
else
{
if (rettv_list_alloc(rettv) == FAIL)
return;
}
/* Always open the file in binary mode, library functions have a mind of /* Always open the file in binary mode, library functions have a mind of
* their own about CR-LF conversion. */ * their own about CR-LF conversion. */
@@ -8960,6 +9079,17 @@ f_readfile(typval_T *argvars, typval_T *rettv)
return; return;
} }
if (blob)
{
if (read_blob(fd, rettv->vval.v_blob) == FAIL)
{
EMSG("cannot read file");
blob_free(rettv->vval.v_blob);
}
fclose(fd);
return;
}
while (cnt < maxline || maxline < 0) while (cnt < maxline || maxline < 0)
{ {
readlen = (int)fread(buf, 1, io_size, fd); readlen = (int)fread(buf, 1, io_size, fd);
@@ -9555,6 +9685,7 @@ f_remove(typval_T *argvars, typval_T *rettv)
dict_T *d; dict_T *d;
dictitem_T *di; dictitem_T *di;
char_u *arg_errmsg = (char_u *)N_("remove() argument"); char_u *arg_errmsg = (char_u *)N_("remove() argument");
int error = FALSE;
if (argvars[0].v_type == VAR_DICT) if (argvars[0].v_type == VAR_DICT)
{ {
@@ -9579,16 +9710,76 @@ f_remove(typval_T *argvars, typval_T *rettv)
} }
} }
} }
else if (argvars[0].v_type == VAR_BLOB)
{
idx = (long)tv_get_number_chk(&argvars[1], &error);
if (!error)
{
blob_T *b = argvars[0].vval.v_blob;
int len = blob_len(b);
char_u *p;
if (idx < 0)
// count from the end
idx = len + idx;
if (idx < 0 || idx >= len)
{
EMSGN(_(e_blobidx), idx);
return;
}
if (argvars[2].v_type == VAR_UNKNOWN)
{
// Remove one item, return its value.
p = (char_u *)b->bv_ga.ga_data;
rettv->vval.v_number = (varnumber_T) *(p + idx);
mch_memmove(p + idx, p + idx + 1, (size_t)len - idx - 1);
--b->bv_ga.ga_len;
}
else
{
blob_T *blob;
// Remove range of items, return list with values.
end = (long)tv_get_number_chk(&argvars[2], &error);
if (error)
return;
if (end < 0)
// count from the end
end = len + end;
if (end >= len || idx > end)
{
EMSGN(_(e_blobidx), end);
return;
}
blob = blob_alloc();
if (blob == NULL)
return;
blob->bv_ga.ga_len = end - idx + 1;
if (ga_grow(&blob->bv_ga, end - idx + 1) == FAIL)
{
vim_free(blob);
return;
}
p = (char_u *)b->bv_ga.ga_data;
mch_memmove((char_u *)blob->bv_ga.ga_data, p + idx,
(size_t)(end - idx + 1));
++blob->bv_refcount;
rettv->v_type = VAR_BLOB;
rettv->vval.v_blob = blob;
mch_memmove(p + idx, p + end + 1, (size_t)(len - end));
b->bv_ga.ga_len -= end - idx + 1;
}
}
}
else if (argvars[0].v_type != VAR_LIST) else if (argvars[0].v_type != VAR_LIST)
EMSG2(_(e_listdictarg), "remove()"); EMSG2(_(e_listdictarg), "remove()");
else if ((l = argvars[0].vval.v_list) != NULL else if ((l = argvars[0].vval.v_list) != NULL
&& !tv_check_lock(l->lv_lock, arg_errmsg, TRUE)) && !tv_check_lock(l->lv_lock, arg_errmsg, TRUE))
{ {
int error = FALSE;
idx = (long)tv_get_number_chk(&argvars[1], &error); idx = (long)tv_get_number_chk(&argvars[1], &error);
if (error) if (error)
; /* type error: do nothing, errmsg already given */ ; // type error: do nothing, errmsg already given
else if ((item = list_find(l, idx)) == NULL) else if ((item = list_find(l, idx)) == NULL)
EMSGN(_(e_listidx), idx); EMSGN(_(e_listidx), idx);
else else
@@ -9602,10 +9793,10 @@ f_remove(typval_T *argvars, typval_T *rettv)
} }
else else
{ {
/* Remove range of items, return list with values. */ // Remove range of items, return list with values.
end = (long)tv_get_number_chk(&argvars[2], &error); end = (long)tv_get_number_chk(&argvars[2], &error);
if (error) if (error)
; /* type error: do nothing */ ; // type error: do nothing
else if ((item2 = list_find(l, end)) == NULL) else if ((item2 = list_find(l, end)) == NULL)
EMSGN(_(e_listidx), end); EMSGN(_(e_listidx), end);
else else
@@ -9912,6 +10103,22 @@ f_reverse(typval_T *argvars, typval_T *rettv)
list_T *l; list_T *l;
listitem_T *li, *ni; listitem_T *li, *ni;
if (argvars[0].v_type == VAR_BLOB)
{
blob_T *b = argvars[0].vval.v_blob;
int i, len = blob_len(b);
for (i = 0; i < len / 2; i++)
{
int tmp = blob_get(b, i);
blob_set(b, i, blob_get(b, len - i - 1));
blob_set(b, len - i - 1, tmp);
}
rettv_blob_set(rettv, b);
return;
}
if (argvars[0].v_type != VAR_LIST) if (argvars[0].v_type != VAR_LIST)
EMSG2(_(e_listarg), "reverse()"); EMSG2(_(e_listarg), "reverse()");
else if ((l = argvars[0].vval.v_list) != NULL else if ((l = argvars[0].vval.v_list) != NULL
@@ -14198,6 +14405,7 @@ f_type(typval_T *argvars, typval_T *rettv)
break; break;
case VAR_JOB: n = VAR_TYPE_JOB; break; case VAR_JOB: n = VAR_TYPE_JOB; break;
case VAR_CHANNEL: n = VAR_TYPE_CHANNEL; break; case VAR_CHANNEL: n = VAR_TYPE_CHANNEL; break;
case VAR_BLOB: n = VAR_TYPE_BLOB; break;
case VAR_UNKNOWN: case VAR_UNKNOWN:
internal_error("f_type(UNKNOWN)"); internal_error("f_type(UNKNOWN)");
n = -1; n = -1;
@@ -14556,23 +14764,33 @@ f_writefile(typval_T *argvars, typval_T *rettv)
FILE *fd; FILE *fd;
int ret = 0; int ret = 0;
listitem_T *li; listitem_T *li;
list_T *list; list_T *list = NULL;
blob_T *blob = NULL;
rettv->vval.v_number = -1; rettv->vval.v_number = -1;
if (check_restricted() || check_secure()) if (check_restricted() || check_secure())
return; return;
if (argvars[0].v_type != VAR_LIST) if (argvars[0].v_type == VAR_LIST)
{ {
EMSG2(_(e_listarg), "writefile()"); list = argvars[0].vval.v_list;
if (list == NULL)
return;
for (li = list->lv_first; li != NULL; li = li->li_next)
if (tv_get_string_chk(&li->li_tv) == NULL)
return;
}
else if (argvars[0].v_type == VAR_BLOB)
{
blob = argvars[0].vval.v_blob;
if (blob == NULL)
return;
}
else
{
EMSG2(_(e_invarg2), "writefile()");
return; return;
} }
list = argvars[0].vval.v_list;
if (list == NULL)
return;
for (li = list->lv_first; li != NULL; li = li->li_next)
if (tv_get_string_chk(&li->li_tv) == NULL)
return;
if (argvars[2].v_type != VAR_UNKNOWN) if (argvars[2].v_type != VAR_UNKNOWN)
{ {
@@ -14604,6 +14822,18 @@ f_writefile(typval_T *argvars, typval_T *rettv)
EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname); EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
ret = -1; ret = -1;
} }
else if (blob)
{
if (write_blob(fd, blob) == FAIL)
ret = -1;
#ifdef HAVE_FSYNC
else if (do_fsync)
// Ignore the error, the user wouldn't know what to do about it.
// May happen for a device.
vim_ignored = fsync(fileno(fd));
#endif
fclose(fd);
}
else else
{ {
if (write_list(fd, list, binary) == FAIL) if (write_list(fd, list, binary) == FAIL)

View File

@@ -1524,6 +1524,8 @@ EXTERN char_u e_readonlysbx[] INIT(= N_("E794: Cannot set variable in the sandbo
EXTERN char_u e_emptykey[] INIT(= N_("E713: Cannot use empty key for Dictionary")); EXTERN char_u e_emptykey[] INIT(= N_("E713: Cannot use empty key for Dictionary"));
EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required")); EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required"));
EXTERN char_u e_listidx[] INIT(= N_("E684: list index out of range: %ld")); EXTERN char_u e_listidx[] INIT(= N_("E684: list index out of range: %ld"));
EXTERN char_u e_blobidx[] INIT(= N_("E979: Blob index out of range: %ld"));
EXTERN char_u e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));
EXTERN char_u e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s")); EXTERN char_u e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s"));
EXTERN char_u e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s")); EXTERN char_u e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s"));
EXTERN char_u e_listreq[] INIT(= N_("E714: List required")); EXTERN char_u e_listreq[] INIT(= N_("E714: List required"));

View File

@@ -236,6 +236,7 @@ typedef int perl_key;
# else # else
# define Perl_sv_2pv dll_Perl_sv_2pv # define Perl_sv_2pv dll_Perl_sv_2pv
# endif # endif
# define Perl_sv_2pvbyte dll_Perl_sv_2pvbyte
# define Perl_sv_bless dll_Perl_sv_bless # define Perl_sv_bless dll_Perl_sv_bless
# if (PERL_REVISION == 5) && (PERL_VERSION >= 8) # if (PERL_REVISION == 5) && (PERL_VERSION >= 8)
# define Perl_sv_catpvn_flags dll_Perl_sv_catpvn_flags # define Perl_sv_catpvn_flags dll_Perl_sv_catpvn_flags
@@ -388,6 +389,7 @@ static char* (*Perl_sv_2pv_nolen)(pTHX_ SV*);
# else # else
static char* (*Perl_sv_2pv)(pTHX_ SV*, STRLEN*); static char* (*Perl_sv_2pv)(pTHX_ SV*, STRLEN*);
# endif # endif
static char* (*Perl_sv_2pvbyte)(pTHX_ SV*, STRLEN*);
static SV* (*Perl_sv_bless)(pTHX_ SV*, HV*); static SV* (*Perl_sv_bless)(pTHX_ SV*, HV*);
# if (PERL_REVISION == 5) && (PERL_VERSION >= 8) # if (PERL_REVISION == 5) && (PERL_VERSION >= 8)
static void (*Perl_sv_catpvn_flags)(pTHX_ SV* , const char*, STRLEN, I32); static void (*Perl_sv_catpvn_flags)(pTHX_ SV* , const char*, STRLEN, I32);
@@ -543,6 +545,7 @@ static struct {
# else # else
{"Perl_sv_2pv", (PERL_PROC*)&Perl_sv_2pv}, {"Perl_sv_2pv", (PERL_PROC*)&Perl_sv_2pv},
# endif # endif
{"Perl_sv_2pvbyte", (PERL_PROC*)&Perl_sv_2pvbyte},
# ifdef PERL589_OR_LATER # ifdef PERL589_OR_LATER
{"Perl_sv_2iv_flags", (PERL_PROC*)&Perl_sv_2iv_flags}, {"Perl_sv_2iv_flags", (PERL_PROC*)&Perl_sv_2iv_flags},
{"Perl_newXS_flags", (PERL_PROC*)&Perl_newXS_flags}, {"Perl_newXS_flags", (PERL_PROC*)&Perl_newXS_flags},
@@ -1556,6 +1559,27 @@ Eval(str)
vim_free(value); vim_free(value);
} }
SV*
Blob(SV* sv)
PREINIT:
STRLEN len;
char *s;
int i;
char buf[3];
SV* newsv;
CODE:
s = SvPVbyte(sv, len);
newsv = newSVpv("0z", 2);
for (i = 0; i < len; i++)
{
sprintf(buf, "%02X", s[i]);
sv_catpvn(newsv, buf, 2);
}
RETVAL = newsv;
OUTPUT:
RETVAL
void void
Buffers(...) Buffers(...)

View File

@@ -867,6 +867,10 @@ VimToPython(typval_T *our_tv, int depth, PyObject *lookup_dict)
} }
return ret; return ret;
} }
else if (our_tv->v_type == VAR_BLOB)
ret = PyBytes_FromStringAndSize(
(char*) our_tv->vval.v_blob->bv_ga.ga_data,
(Py_ssize_t) our_tv->vval.v_blob->bv_ga.ga_len);
else else
{ {
Py_INCREF(Py_None); Py_INCREF(Py_None);
@@ -6394,6 +6398,10 @@ ConvertToPyObject(typval_T *tv)
tv->vval.v_partial->pt_argc, argv, tv->vval.v_partial->pt_argc, argv,
tv->vval.v_partial->pt_dict, tv->vval.v_partial->pt_dict,
tv->vval.v_partial->pt_auto); tv->vval.v_partial->pt_auto);
case VAR_BLOB:
return PyBytes_FromStringAndSize(
(char*) tv->vval.v_blob->bv_ga.ga_data,
(Py_ssize_t) tv->vval.v_blob->bv_ga.ga_len);
case VAR_UNKNOWN: case VAR_UNKNOWN:
case VAR_CHANNEL: case VAR_CHANNEL:
case VAR_JOB: case VAR_JOB:

View File

@@ -1575,6 +1575,7 @@ do_pyeval (char_u *str, typval_T *rettv)
case VAR_SPECIAL: case VAR_SPECIAL:
case VAR_JOB: case VAR_JOB:
case VAR_CHANNEL: case VAR_CHANNEL:
case VAR_BLOB:
break; break;
} }
} }

View File

@@ -232,6 +232,8 @@ typedef PySliceObject PySliceObject_T;
# endif # endif
# undef PyBytes_FromString # undef PyBytes_FromString
# define PyBytes_FromString py3_PyBytes_FromString # define PyBytes_FromString py3_PyBytes_FromString
# undef PyBytes_FromStringAndSize
# define PyBytes_FromStringAndSize py3_PyBytes_FromStringAndSize
# define PyFloat_FromDouble py3_PyFloat_FromDouble # define PyFloat_FromDouble py3_PyFloat_FromDouble
# define PyFloat_AsDouble py3_PyFloat_AsDouble # define PyFloat_AsDouble py3_PyFloat_AsDouble
# define PyObject_GenericGetAttr py3_PyObject_GenericGetAttr # define PyObject_GenericGetAttr py3_PyObject_GenericGetAttr
@@ -394,6 +396,7 @@ static PyObject* (*py3_PyUnicode_AsEncodedString)(PyObject *unicode, const char*
static char* (*py3_PyBytes_AsString)(PyObject *bytes); static char* (*py3_PyBytes_AsString)(PyObject *bytes);
static int (*py3_PyBytes_AsStringAndSize)(PyObject *bytes, char **buffer, Py_ssize_t *length); static int (*py3_PyBytes_AsStringAndSize)(PyObject *bytes, char **buffer, Py_ssize_t *length);
static PyObject* (*py3_PyBytes_FromString)(char *str); static PyObject* (*py3_PyBytes_FromString)(char *str);
static PyObject* (*py3_PyBytes_FromStringAndSize)(char *str, Py_ssize_t length);
static PyObject* (*py3_PyFloat_FromDouble)(double num); static PyObject* (*py3_PyFloat_FromDouble)(double num);
static double (*py3_PyFloat_AsDouble)(PyObject *); static double (*py3_PyFloat_AsDouble)(PyObject *);
static PyObject* (*py3_PyObject_GenericGetAttr)(PyObject *obj, PyObject *name); static PyObject* (*py3_PyObject_GenericGetAttr)(PyObject *obj, PyObject *name);
@@ -559,6 +562,7 @@ static struct
{"PyBytes_AsString", (PYTHON_PROC*)&py3_PyBytes_AsString}, {"PyBytes_AsString", (PYTHON_PROC*)&py3_PyBytes_AsString},
{"PyBytes_AsStringAndSize", (PYTHON_PROC*)&py3_PyBytes_AsStringAndSize}, {"PyBytes_AsStringAndSize", (PYTHON_PROC*)&py3_PyBytes_AsStringAndSize},
{"PyBytes_FromString", (PYTHON_PROC*)&py3_PyBytes_FromString}, {"PyBytes_FromString", (PYTHON_PROC*)&py3_PyBytes_FromString},
{"PyBytes_FromStringAndSize", (PYTHON_PROC*)&py3_PyBytes_FromStringAndSize},
{"PyFloat_FromDouble", (PYTHON_PROC*)&py3_PyFloat_FromDouble}, {"PyFloat_FromDouble", (PYTHON_PROC*)&py3_PyFloat_FromDouble},
{"PyFloat_AsDouble", (PYTHON_PROC*)&py3_PyFloat_AsDouble}, {"PyFloat_AsDouble", (PYTHON_PROC*)&py3_PyFloat_AsDouble},
{"PyObject_GenericGetAttr", (PYTHON_PROC*)&py3_PyObject_GenericGetAttr}, {"PyObject_GenericGetAttr", (PYTHON_PROC*)&py3_PyObject_GenericGetAttr},
@@ -1680,6 +1684,7 @@ do_py3eval (char_u *str, typval_T *rettv)
case VAR_SPECIAL: case VAR_SPECIAL:
case VAR_JOB: case VAR_JOB:
case VAR_CHANNEL: case VAR_CHANNEL:
case VAR_BLOB:
break; break;
} }
} }

View File

@@ -51,6 +51,7 @@
# define rb_cFloat (*dll_rb_cFloat) # define rb_cFloat (*dll_rb_cFloat)
# endif # endif
# define rb_cNilClass (*dll_rb_cNilClass) # define rb_cNilClass (*dll_rb_cNilClass)
# define rb_cString (*dll_rb_cString)
# define rb_cSymbol (*dll_rb_cSymbol) # define rb_cSymbol (*dll_rb_cSymbol)
# define rb_cTrueClass (*dll_rb_cTrueClass) # define rb_cTrueClass (*dll_rb_cTrueClass)
# if defined(DYNAMIC_RUBY_VER) && DYNAMIC_RUBY_VER >= 18 # if defined(DYNAMIC_RUBY_VER) && DYNAMIC_RUBY_VER >= 18
@@ -219,6 +220,7 @@ static void ruby_vim_init(void);
*/ */
# define rb_assoc_new dll_rb_assoc_new # define rb_assoc_new dll_rb_assoc_new
# define rb_cObject (*dll_rb_cObject) # define rb_cObject (*dll_rb_cObject)
# define rb_class_new_instance dll_rb_class_new_instance
# define rb_check_type dll_rb_check_type # define rb_check_type dll_rb_check_type
# ifdef USE_TYPEDDATA # ifdef USE_TYPEDDATA
# define rb_check_typeddata dll_rb_check_typeddata # define rb_check_typeddata dll_rb_check_typeddata
@@ -365,8 +367,10 @@ VALUE *dll_rb_cFloat;
# endif # endif
VALUE *dll_rb_cNilClass; VALUE *dll_rb_cNilClass;
static VALUE *dll_rb_cObject; static VALUE *dll_rb_cObject;
VALUE *dll_rb_cString;
VALUE *dll_rb_cSymbol; VALUE *dll_rb_cSymbol;
VALUE *dll_rb_cTrueClass; VALUE *dll_rb_cTrueClass;
static VALUE (*dll_rb_class_new_instance) (int,VALUE*,VALUE);
static void (*dll_rb_check_type) (VALUE,int); static void (*dll_rb_check_type) (VALUE,int);
# ifdef USE_TYPEDDATA # ifdef USE_TYPEDDATA
static void *(*dll_rb_check_typeddata) (VALUE,const rb_data_type_t *); static void *(*dll_rb_check_typeddata) (VALUE,const rb_data_type_t *);
@@ -579,8 +583,10 @@ static struct
# endif # endif
{"rb_cNilClass", (RUBY_PROC*)&dll_rb_cNilClass}, {"rb_cNilClass", (RUBY_PROC*)&dll_rb_cNilClass},
{"rb_cObject", (RUBY_PROC*)&dll_rb_cObject}, {"rb_cObject", (RUBY_PROC*)&dll_rb_cObject},
{"rb_cString", (RUBY_PROC*)&dll_rb_cString},
{"rb_cSymbol", (RUBY_PROC*)&dll_rb_cSymbol}, {"rb_cSymbol", (RUBY_PROC*)&dll_rb_cSymbol},
{"rb_cTrueClass", (RUBY_PROC*)&dll_rb_cTrueClass}, {"rb_cTrueClass", (RUBY_PROC*)&dll_rb_cTrueClass},
{"rb_class_new_instance", (RUBY_PROC*)&dll_rb_class_new_instance},
{"rb_check_type", (RUBY_PROC*)&dll_rb_check_type}, {"rb_check_type", (RUBY_PROC*)&dll_rb_check_type},
# ifdef USE_TYPEDDATA # ifdef USE_TYPEDDATA
{"rb_check_typeddata", (RUBY_PROC*)&dll_rb_check_typeddata}, {"rb_check_typeddata", (RUBY_PROC*)&dll_rb_check_typeddata},
@@ -1164,7 +1170,13 @@ static VALUE vim_to_ruby(typval_T *tv)
result = Qtrue; result = Qtrue;
else if (tv->vval.v_number == VVAL_FALSE) else if (tv->vval.v_number == VVAL_FALSE)
result = Qfalse; result = Qfalse;
} /* else return Qnil; */ }
else if (tv->v_type == VAR_BLOB)
{
result = rb_str_new(tv->vval.v_blob->bv_ga.ga_data,
tv->vval.v_blob->bv_ga.ga_len);
}
/* else return Qnil; */
return result; return result;
} }
@@ -1242,6 +1254,19 @@ static buf_T *get_buf(VALUE obj)
return buf; return buf;
} }
static VALUE vim_blob(VALUE self UNUSED, VALUE str)
{
VALUE result = rb_str_new("0z", 2);
char buf[4];
int i;
for (i = 0; i < RSTRING_LEN(str); i++)
{
sprintf(buf, "%02X", RSTRING_PTR(str)[i]);
rb_str_concat(result, rb_str_new_cstr(buf));
}
return result;
}
static VALUE buffer_s_current(void) static VALUE buffer_s_current(void)
{ {
return buffer_new(curbuf); return buffer_new(curbuf);
@@ -1662,6 +1687,7 @@ static void ruby_vim_init(void)
rb_define_module_function(mVIM, "set_option", vim_set_option, 1); rb_define_module_function(mVIM, "set_option", vim_set_option, 1);
rb_define_module_function(mVIM, "command", vim_command, 1); rb_define_module_function(mVIM, "command", vim_command, 1);
rb_define_module_function(mVIM, "evaluate", vim_evaluate, 1); rb_define_module_function(mVIM, "evaluate", vim_evaluate, 1);
rb_define_module_function(mVIM, "blob", vim_blob, 1);
eDeletedBufferError = rb_define_class_under(mVIM, "DeletedBufferError", eDeletedBufferError = rb_define_class_under(mVIM, "DeletedBufferError",
rb_eStandardError); rb_eStandardError);

View File

@@ -195,8 +195,10 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
{ {
char_u numbuf[NUMBUFLEN]; char_u numbuf[NUMBUFLEN];
char_u *res; char_u *res;
blob_T *b;
list_T *l; list_T *l;
dict_T *d; dict_T *d;
int i;
switch (val->v_type) switch (val->v_type)
{ {
@@ -233,6 +235,25 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
EMSG(_(e_invarg)); EMSG(_(e_invarg));
return FAIL; return FAIL;
case VAR_BLOB:
b = val->vval.v_blob;
if (b == NULL || b->bv_ga.ga_len == 0)
ga_concat(gap, (char_u *)"[]");
else
{
ga_append(gap, '[');
for (i = 0; i < b->bv_ga.ga_len; i++)
{
if (i > 0)
ga_concat(gap, (char_u *)",");
vim_snprintf((char *)numbuf, NUMBUFLEN, "%d",
(int)blob_get(b, i));
ga_concat(gap, numbuf);
}
ga_append(gap, ']');
}
break;
case VAR_LIST: case VAR_LIST:
l = val->vval.v_list; l = val->vval.v_list;
if (l == NULL) if (l == NULL)

View File

@@ -404,7 +404,7 @@ netbeans_parse_messages(void)
if (*p == NUL) if (*p == NUL)
{ {
own_node = TRUE; own_node = TRUE;
buffer = channel_get(nb_channel, PART_SOCK); buffer = channel_get(nb_channel, PART_SOCK, NULL);
/* "node" is now invalid! */ /* "node" is now invalid! */
} }
else else

View File

@@ -88,6 +88,7 @@ extern int _stricoll(char *a, char *b);
# include "hashtab.pro" # include "hashtab.pro"
# include "json.pro" # include "json.pro"
# include "list.pro" # include "list.pro"
# include "blob.pro"
# include "main.pro" # include "main.pro"
# include "mark.pro" # include "mark.pro"
# include "memfile.pro" # include "memfile.pro"

13
src/proto/blob.pro Normal file
View File

@@ -0,0 +1,13 @@
/* blob.c */
blob_T *blob_alloc(void);
int rettv_blob_alloc(typval_T *rettv);
void rettv_blob_set(typval_T *rettv, blob_T *b);
void blob_free(blob_T *b);
void blob_unref(blob_T *b);
long blob_len(blob_T *b);
char_u blob_get(blob_T *b, int idx);
void blob_set(blob_T *b, int idx, char_u c);
int blob_equal(blob_T *b1, blob_T *b2);
int read_blob(FILE *fd, blob_T *blob);
int write_blob(FILE *fd, blob_T *blob);
/* vim: set ft=c : */

View File

@@ -18,7 +18,7 @@ void channel_write_any_lines(void);
void channel_write_new_lines(buf_T *buf); void channel_write_new_lines(buf_T *buf);
readq_T *channel_peek(channel_T *channel, ch_part_T part); readq_T *channel_peek(channel_T *channel, ch_part_T part);
char_u *channel_first_nl(readq_T *node); char_u *channel_first_nl(readq_T *node);
char_u *channel_get(channel_T *channel, ch_part_T part); char_u *channel_get(channel_T *channel, ch_part_T part, int *outlen);
void channel_consume(channel_T *channel, ch_part_T part, int len); void channel_consume(channel_T *channel, ch_part_T part, int len);
int channel_collapse(channel_T *channel, ch_part_T part, int want_nl); int channel_collapse(channel_T *channel, ch_part_T part, int want_nl);
int channel_can_write_to(channel_T *channel); int channel_can_write_to(channel_T *channel);
@@ -30,7 +30,7 @@ void channel_close(channel_T *channel, int invoke_close_cb);
void channel_close_in(channel_T *channel); void channel_close_in(channel_T *channel);
void channel_clear(channel_T *channel); void channel_clear(channel_T *channel);
void channel_free_all(void); void channel_free_all(void);
void common_channel_read(typval_T *argvars, typval_T *rettv, int raw); void common_channel_read(typval_T *argvars, typval_T *rettv, int raw, int blob);
channel_T *channel_fd2channel(sock_T fd, ch_part_T *partp); channel_T *channel_fd2channel(sock_T fd, ch_part_T *partp);
void channel_handle_events(int only_keep_open); void channel_handle_events(int only_keep_open);
int channel_any_keep_open(void); int channel_any_keep_open(void);

View File

@@ -1251,6 +1251,7 @@ typedef double float_T;
typedef struct listvar_S list_T; typedef struct listvar_S list_T;
typedef struct dictvar_S dict_T; typedef struct dictvar_S dict_T;
typedef struct partial_S partial_T; typedef struct partial_S partial_T;
typedef struct blobvar_S blob_T;
typedef struct jobvar_S job_T; typedef struct jobvar_S job_T;
typedef struct readq_S readq_T; typedef struct readq_S readq_T;
@@ -1272,6 +1273,7 @@ typedef enum
VAR_SPECIAL, // "v_number" is used VAR_SPECIAL, // "v_number" is used
VAR_JOB, // "v_job" is used VAR_JOB, // "v_job" is used
VAR_CHANNEL, // "v_channel" is used VAR_CHANNEL, // "v_channel" is used
VAR_BLOB, // "v_blob" is used
} vartype_T; } vartype_T;
/* /*
@@ -1295,6 +1297,7 @@ typedef struct
job_T *v_job; /* job value (can be NULL!) */ job_T *v_job; /* job value (can be NULL!) */
channel_T *v_channel; /* channel value (can be NULL!) */ channel_T *v_channel; /* channel value (can be NULL!) */
#endif #endif
blob_T *v_blob; /* blob value (can be NULL!) */
} vval; } vval;
} typval_T; } typval_T;
@@ -1401,6 +1404,16 @@ struct dictvar_S
dict_T *dv_used_prev; /* previous dict in used dicts list */ dict_T *dv_used_prev; /* previous dict in used dicts list */
}; };
/*
* Structure to hold info about a blob.
*/
struct blobvar_S
{
garray_T bv_ga; // growarray with the data
int bv_refcount; // reference count
char bv_lock; // zero, VAR_LOCKED, VAR_FIXED
};
#if defined(FEAT_EVAL) || defined(PROTO) #if defined(FEAT_EVAL) || defined(PROTO)
typedef struct funccall_S funccall_T; typedef struct funccall_S funccall_T;
@@ -3526,6 +3539,7 @@ typedef struct lval_S
dict_T *ll_dict; /* The Dictionary or NULL */ dict_T *ll_dict; /* The Dictionary or NULL */
dictitem_T *ll_di; /* The dictitem or NULL */ dictitem_T *ll_di; /* The dictitem or NULL */
char_u *ll_newkey; /* New key for Dict in alloc. mem or NULL. */ char_u *ll_newkey; /* New key for Dict in alloc. mem or NULL. */
blob_T *ll_blob; /* The Blob or NULL */
} lval_T; } lval_T;
/* Structure used to save the current state. Used when executing Normal mode /* Structure used to save the current state. Used when executing Normal mode

View File

@@ -73,6 +73,7 @@ NEW_TESTS = \
test_backspace_opt \ test_backspace_opt \
test_backup \ test_backup \
test_behave \ test_behave \
test_blob \
test_blockedit \ test_blockedit \
test_breakindent \ test_breakindent \
test_bufline \ test_bufline \
@@ -283,6 +284,7 @@ NEW_TESTS_RES = \
test_autocmd.res \ test_autocmd.res \
test_autoload.res \ test_autoload.res \
test_backspace_opt.res \ test_backspace_opt.res \
test_blob.res \
test_blockedit.res \ test_blockedit.res \
test_breakindent.res \ test_breakindent.res \
test_bufwintabinfo.res \ test_bufwintabinfo.res \

179
src/testdir/test_blob.vim Normal file
View File

@@ -0,0 +1,179 @@
" Tests for the Blob types
func TearDown()
" Run garbage collection after every test
call test_garbagecollect_now()
endfunc
" Tests for Blob type
" Blob creation from constant
func Test_blob_create()
let b = 0zDEADBEEF
call assert_equal(v:t_blob, type(b))
call assert_equal(4, len(b))
call assert_equal(0xDE, b[0])
call assert_equal(0xAD, b[1])
call assert_equal(0xBE, b[2])
call assert_equal(0xEF, b[3])
call assert_fails('let x = b[4]')
call assert_equal(0xDE, get(b, 0))
call assert_equal(0xEF, get(b, 3))
call assert_fails('let x = get(b, 4)')
endfunc
" assignment to a blob
func Test_blob_assign()
let b = 0zDEADBEEF
let b2 = b[1:2]
call assert_equal(0zADBE, b2)
let bcopy = b[:]
call assert_equal(b, bcopy)
call assert_false(b is bcopy)
endfunc
func Test_blob_to_string()
let b = 0zDEADBEEF
call assert_equal('[0xDE,0xAD,0xBE,0xEF]', string(b))
call remove(b, 0, 3)
call assert_equal('[]', string(b))
endfunc
func Test_blob_compare()
let b1 = 0z0011
let b2 = 0z1100
call assert_false(b1 == b2)
call assert_true(b1 != b2)
call assert_true(b1 == 0z0011)
call assert_false(b1 is b2)
let b2 = b1
call assert_true(b1 is b2)
call assert_fails('let x = b1 > b2')
call assert_fails('let x = b1 < b2')
call assert_fails('let x = b1 - b2')
call assert_fails('let x = b1 / b2')
call assert_fails('let x = b1 * b2')
endfunc
" test for range assign
func Test_blob_range_assign()
let b = 0z00
let b[1] = 0x11
let b[2] = 0x22
call assert_equal(0z001122, b)
call assert_fails('let b[4] = 0x33')
endfunc
func Test_blob_for_loop()
let blob = 0z00010203
let i = 0
for byte in blob
call assert_equal(i, byte)
let i += 1
endfor
let blob = 0z00
call remove(blob, 0)
call assert_equal(0, len(blob))
for byte in blob
call assert_error('loop over empty blob')
endfor
endfunc
func Test_blob_concatenate()
let b = 0z0011
let b += 0z2233
call assert_equal(0z00112233, b)
call assert_fails('let b += "a"')
call assert_fails('let b += 88')
let b = 0zDEAD + 0zBEEF
call assert_equal(0zDEADBEEF, b)
endfunc
" Test removing items in blob
func Test_blob_func_remove()
" Test removing 1 element
let b = 0zDEADBEEF
call assert_equal(0xDE, remove(b, 0))
call assert_equal(0zADBEEF, b)
let b = 0zDEADBEEF
call assert_equal(0xEF, remove(b, -1))
call assert_equal(0zDEADBE, b)
let b = 0zDEADBEEF
call assert_equal(0xAD, remove(b, 1))
call assert_equal(0zDEBEEF, b)
" Test removing range of element(s)
let b = 0zDEADBEEF
call assert_equal(0zBE, remove(b, 2, 2))
call assert_equal(0zDEADEF, b)
let b = 0zDEADBEEF
call assert_equal(0zADBE, remove(b, 1, 2))
call assert_equal(0zDEEF, b)
" Test invalid cases
let b = 0zDEADBEEF
call assert_fails("call remove(b, 5)", 'E979:')
call assert_fails("call remove(b, 1, 5)", 'E979:')
call assert_fails("call remove(b, 3, 2)", 'E979:')
call assert_fails("call remove(1, 0)", 'E712:')
call assert_fails("call remove(b, b)", 'E974:')
endfunc
func Test_blob_read_write()
let b = 0zDEADBEEF
call writefile(b, 'Xblob')
let br = readfile('Xblob', 'B')
call assert_equal(b, br)
call delete('Xblob')
endfunc
" filter() item in blob
func Test_blob_filter()
let b = 0zDEADBEEF
call filter(b, 'v:val != 0xEF')
call assert_equal(0zDEADBE, b)
endfunc
" map() item in blob
func Test_blob_map()
let b = 0zDEADBEEF
call map(b, 'v:val + 1')
call assert_equal(0zDFAEBFF0, b)
endfunc
func Test_blob_index()
call assert_equal(2, index(0zDEADBEEF, 0xBE))
call assert_equal(-1, index(0zDEADBEEF, 0))
endfunc
func Test_blob_insert()
let b = 0zDEADBEEF
call insert(b, 0x33)
call assert_equal(0z33DEADBEEF, b)
let b = 0zDEADBEEF
call insert(b, 0x33, 2)
call assert_equal(0zDEAD33BEEF, b)
endfunc
func Test_blob_reverse()
call assert_equal(0zEFBEADDE, reverse(0zDEADBEEF))
call assert_equal(0zBEADDE, reverse(0zDEADBE))
call assert_equal(0zADDE, reverse(0zDEAD))
call assert_equal(0zDE, reverse(0zDE))
endfunc
func Test_blob_json_encode()
call assert_equal('[222,173,190,239]', json_encode(0zDEADBEEF))
call assert_equal('[]', json_encode(0z))
endfunc

View File

@@ -516,6 +516,51 @@ func Test_raw_pipe()
call assert_equal(1, found) call assert_equal(1, found)
endfunc endfunc
func Test_raw_pipe_blob()
if !has('job')
return
endif
call ch_log('Test_raw_pipe_blob()')
" Add a dummy close callback to avoid that messages are dropped when calling
" ch_canread().
" Also test the non-blocking option.
let job = job_start(s:python . " test_channel_pipe.py",
\ {'mode': 'raw', 'drop': 'never', 'noblock': 1})
call assert_equal(v:t_job, type(job))
call assert_equal("run", job_status(job))
call assert_equal("open", ch_status(job))
call assert_equal("open", ch_status(job), {"part": "out"})
try
" Create a blob with the echo command and write it.
let blob = 0z00
let cmd = "echo something\n"
for i in range(0, len(cmd) - 1)
let blob[i] = char2nr(cmd[i])
endfor
call assert_equal(len(cmd), len(blob))
call ch_sendraw(job, blob)
" Read a blob with the reply.
let msg = ch_readblob(job)
let expected = 'something'
for i in range(0, len(expected) - 1)
call assert_equal(char2nr(expected[i]), msg[i])
endfor
let reply = ch_evalraw(job, "quit\n", {'timeout': 100})
call assert_equal("Goodbye!\n", substitute(reply, "\r", "", 'g'))
finally
call job_stop(job)
endtry
let g:Ch_job = job
call WaitForAssert({-> assert_equal("dead", job_status(g:Ch_job))})
let info = job_info(job)
call assert_equal("dead", info.status)
endfunc
func Test_nl_pipe() func Test_nl_pipe()
if !has('job') if !has('job')
return return

View File

@@ -795,6 +795,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
735,
/**/ /**/
734, 734,
/**/ /**/

View File

@@ -1994,13 +1994,14 @@ typedef int sock_T;
#define VV_TYPE_NONE 78 #define VV_TYPE_NONE 78
#define VV_TYPE_JOB 79 #define VV_TYPE_JOB 79
#define VV_TYPE_CHANNEL 80 #define VV_TYPE_CHANNEL 80
#define VV_TERMRFGRESP 81 #define VV_TYPE_BLOB 81
#define VV_TERMRBGRESP 82 #define VV_TERMRFGRESP 82
#define VV_TERMU7RESP 83 #define VV_TERMRBGRESP 83
#define VV_TERMSTYLERESP 84 #define VV_TERMU7RESP 84
#define VV_TERMBLINKRESP 85 #define VV_TERMSTYLERESP 85
#define VV_EVENT 86 #define VV_TERMBLINKRESP 86
#define VV_LEN 87 /* number of v: vars */ #define VV_EVENT 87
#define VV_LEN 88 /* number of v: vars */
/* used for v_number in VAR_SPECIAL */ /* used for v_number in VAR_SPECIAL */
#define VVAL_FALSE 0L #define VVAL_FALSE 0L
@@ -2019,6 +2020,7 @@ typedef int sock_T;
#define VAR_TYPE_NONE 7 #define VAR_TYPE_NONE 7
#define VAR_TYPE_JOB 8 #define VAR_TYPE_JOB 8
#define VAR_TYPE_CHANNEL 9 #define VAR_TYPE_CHANNEL 9
#define VAR_TYPE_BLOB 10
#ifdef FEAT_CLIPBOARD #ifdef FEAT_CLIPBOARD