Repository: microsoft/ptvsd

Issue:
Cannot edit collection elements



Discussion:
This appears to be a pydevd bug, where editing a value of, say, the first item in the list executes Python code along the lines of `lines.0 = 123` instead of `lines[0] = 123`.
I tried changing code in `pydevd` to generate `lines[0]`. Instead of updating the first item in the `lines` collection. It actually added a new item to the `FRAME` named `lines[0]`. Because, the variables are stored in `frame.f_locals[attr] = value`.
I think it's because for `CMD_CHANGE_VARIABLE`, you need to specify the children in the same way as you do for `CMD_GET_VARIABLE` (i.e. tab-separated like `foo\tbar\t1` for `foo.bar[1]`).

It looks like we'll just have to fix it on pydevd side of things, by accounting for indices in the handling of `CMD_CHANGE_VARIABLE`.
Actually, to clarify - when you changed that code, did you change it to generate just a single `lines[0]`, or tab-separated `lines` and `[0]`?
The command args looked like this: `<tid>\t<fid>\tFRAME\tlines[0]\t1234`. The issue is actually in `pydevd_vars.py`. Get variable code uses type resolver to get to the actual value. Where as set variable does not use type resolver, instead it directly attempts to change the value.
This is the result of setVariable with following inputs:
`['pid_26312_id_1588235631248', '1588252057672', 'EXPRESSION', 'lines[0]', '22']`

Result:
![image](https://user-images.githubusercontent.com/3840081/37312620-ffa6f6ae-2608-11e8-8c03-64b94292a7b1.png)

Instead of updating, it created a new variable.

And for this case:
`['pid_3364_id_2123742022344', '2123757001656', 'EXPRESSION', 'lines', '[0]', '22']`

you will get this error:
```
Traceback (most recent call last):
  File "G:\git\ptvsd\ptvsd\pydevd\_pydevd_bundle\pydevd_vars.py", line 396, in change_attr_expression
    Exec('%s=%s' % (attr, expression), frame.f_globals, frame.f_locals)
  File "G:\git\ptvsd\ptvsd\pydevd\_pydevd_bundle\pydevd_exec2.py", line 3, in Exec
    exec(exp, global_vars, local_vars)
  File "<string>", line 1
    lines.[0]=22
          ^
```



Resolving patch:
diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py
--- a/ptvsd/wrapper.py
+++ b/ptvsd/wrapper.py
@@ -835,18 +835,38 @@ def __get_index_or_key(self, text):
     def on_setVariable(self, request, args):
         vsc_var = int(args['variablesReference'])
         pyd_var = self.var_map.to_pydevd(vsc_var)
+        
+        var_name = args['name']
+        var_value = args['value']
+        
+        lhs_expr = self.__get_variable_evaluate_name(pyd_var, var_name)
+        if not lhs_expr:
+            lhs_expr = var_name
+        expr = '%s = %s' % (lhs_expr, var_value)
+        # pydevd message format doesn't permit tabs in expressions
+        expr = expr.replace('\t', ' ')
+
+        pyd_tid = str(pyd_var[0])
+        pyd_fid = str(pyd_var[1])
 
         # VSC gives us variablesReference to the parent of the variable
         # being set, and variable name; but pydevd wants the ID
         # (or rather path) of the variable itself.
-        pyd_var += (args['name'],)
+        pyd_var += (var_name,)
         vsc_var = self.var_map.to_vscode(pyd_var, autogen=True)
 
-        cmd_args = [str(s) for s in pyd_var] + [args['value']]
+        cmd_args = [pyd_tid, pyd_fid, 'LOCAL', expr, '1']
+        yield self.pydevd_request(
+            pydevd_comm.CMD_EXEC_EXPRESSION,
+            '\t'.join(cmd_args),
+        )
+
+        cmd_args = [pyd_tid, pyd_fid, 'LOCAL', lhs_expr, '1']
         _, _, resp_args = yield self.pydevd_request(
-            pydevd_comm.CMD_CHANGE_VARIABLE,
+            pydevd_comm.CMD_EVALUATE_EXPRESSION,
             '\t'.join(cmd_args),
         )
+
         xml = untangle.parse(resp_args).xml
         xvar = xml.var
 


Test patch: