diff --git a/lib/mp-readline/readline.c b/lib/mp-readline/readline.c
index ce2a75905aba8006de7f1742ec5eb41f28090ae9..f119fb620ac3ee693b7634b18d853555412319c5 100644
--- a/lib/mp-readline/readline.c
+++ b/lib/mp-readline/readline.c
@@ -29,6 +29,7 @@
 #include <string.h>
 
 #include "py/mpstate.h"
+#include "py/repl.h"
 #include "readline.h"
 #ifdef MICROPY_HAL_H
 #include MICROPY_HAL_H
@@ -134,6 +135,28 @@ int readline_process_char(int c) {
                 redraw_step_back = 1;
                 redraw_from_cursor = true;
             }
+        #if MICROPY_HELPER_REPL
+        } else if (c == 9) {
+            // tab magic
+            const char *compl_str;
+            mp_uint_t compl_len = mp_repl_autocomplete(rl.line->buf + rl.orig_line_len, rl.cursor_pos - rl.orig_line_len, &mp_plat_print, &compl_str);
+            if (compl_len == 0) {
+                // no match
+            } else if (compl_len == (mp_uint_t)(-1)) {
+                // many matches
+                mp_hal_stdout_tx_str(rl.prompt);
+                mp_hal_stdout_tx_strn(rl.line->buf + rl.orig_line_len, rl.cursor_pos - rl.orig_line_len);
+                redraw_from_cursor = true;
+            } else {
+                // one match
+                for (int i = 0; i < compl_len; ++i) {
+                    vstr_ins_byte(rl.line, rl.cursor_pos + i, *compl_str++);
+                }
+                // set redraw parameters
+                redraw_from_cursor = true;
+                redraw_step_forward = compl_len;
+            }
+        #endif
         } else if (32 <= c && c <= 126) {
             // printable character
             vstr_ins_char(rl.line, rl.cursor_pos, c);
diff --git a/py/repl.c b/py/repl.c
index c6b3e60ae165a6a56e9d766feab04b66cd34d847..b03f0ed9850f7961ef13ad1f414e6e54b3b6181d 100644
--- a/py/repl.c
+++ b/py/repl.c
@@ -3,7 +3,7 @@
  *
  * The MIT License (MIT)
  *
- * Copyright (c) 2013, 2014 Damien P. George
+ * Copyright (c) 2013-2015 Damien P. George
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -24,6 +24,9 @@
  * THE SOFTWARE.
  */
 
+#include <string.h>
+#include "py/obj.h"
+#include "py/runtime.h"
 #include "py/repl.h"
 
 #if MICROPY_HELPER_REPL
@@ -105,4 +108,142 @@ bool mp_repl_continue_with_input(const char *input) {
     return false;
 }
 
+mp_uint_t mp_repl_autocomplete(const char *str, mp_uint_t len, const mp_print_t *print, const char **compl_str) {
+    // scan backwards to find start of "a.b.c" chain
+    const char *top = str + len;
+    for (const char *s = top; --s >= str;) {
+        if (!(unichar_isalpha(*s) || unichar_isdigit(*s) || *s == '_' || *s == '.')) {
+            ++s;
+            str = s;
+            break;
+        }
+    }
+
+    // begin search in locals dict
+    mp_obj_dict_t *dict = mp_locals_get();
+
+    for (;;) {
+        // get next word in string to complete
+        const char *s_start = str;
+        while (str < top && *str != '.') {
+            ++str;
+        }
+        mp_uint_t s_len = str - s_start;
+
+        if (str < top) {
+            // a complete word, lookup in current dict
+
+            mp_obj_t obj = MP_OBJ_NULL;
+            for (mp_uint_t i = 0; i < dict->map.alloc; i++) {
+                if (MP_MAP_SLOT_IS_FILLED(&dict->map, i)) {
+                    mp_uint_t d_len;
+                    const char *d_str = mp_obj_str_get_data(dict->map.table[i].key, &d_len);
+                    if (s_len == d_len && strncmp(s_start, d_str, d_len) == 0) {
+                        obj = dict->map.table[i].value;
+                        break;
+                    }
+                }
+            }
+
+            if (obj == MP_OBJ_NULL) {
+                // lookup failed
+                return 0;
+            }
+
+            // found an object of this name; try to get its dict
+            if (MP_OBJ_IS_TYPE(obj, &mp_type_module)) {
+                dict = mp_obj_module_get_globals(obj);
+            } else {
+                mp_obj_type_t *type;
+                if (MP_OBJ_IS_TYPE(obj, &mp_type_type)) {
+                    type = obj;
+                } else {
+                    type = mp_obj_get_type(obj);
+                }
+                if (type->locals_dict != MP_OBJ_NULL && MP_OBJ_IS_TYPE(type->locals_dict, &mp_type_dict)) {
+                    dict = type->locals_dict;
+                } else {
+                    // obj has no dict
+                    return 0;
+                }
+            }
+
+            // skip '.' to move to next word
+            ++str;
+
+        } else {
+            // end of string, do completion on this partial name
+
+            // look for matches
+            int n_found = 0;
+            const char *match_str = NULL;
+            mp_uint_t match_len = 0;
+            for (mp_uint_t i = 0; i < dict->map.alloc; i++) {
+                if (MP_MAP_SLOT_IS_FILLED(&dict->map, i)) {
+                    mp_uint_t d_len;
+                    const char *d_str = mp_obj_str_get_data(dict->map.table[i].key, &d_len);
+                    if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
+                        if (match_str == NULL) {
+                            match_str = d_str;
+                            match_len = d_len;
+                        } else {
+                            for (mp_uint_t i = s_len; i < match_len && i < d_len; ++i) {
+                                if (match_str[i] != d_str[i]) {
+                                    match_len = i;
+                                    break;
+                                }
+                            }
+                        }
+                        ++n_found;
+                    }
+                }
+            }
+
+            // nothing found
+            if (n_found == 0) {
+                return 0;
+            }
+
+            // 1 match found, or multiple matches with a common prefix
+            if (n_found == 1 || match_len > s_len) {
+                *compl_str = match_str + s_len;
+                return match_len - s_len;
+            }
+
+            // multiple matches found, print them out
+
+            #define WORD_SLOT_LEN (16)
+            #define MAX_LINE_LEN  (4 * WORD_SLOT_LEN)
+
+            int line_len = MAX_LINE_LEN; // force a newline for first word
+            for (mp_uint_t i = 0; i < dict->map.alloc; i++) {
+                if (MP_MAP_SLOT_IS_FILLED(&dict->map, i)) {
+                    mp_uint_t d_len;
+                    const char *d_str = mp_obj_str_get_data(dict->map.table[i].key, &d_len);
+                    if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
+                        int gap = (line_len + WORD_SLOT_LEN - 1) / WORD_SLOT_LEN * WORD_SLOT_LEN - line_len;
+                        if (gap < 2) {
+                            gap += WORD_SLOT_LEN;
+                        }
+                        if (line_len + gap + d_len <= MAX_LINE_LEN) {
+                            // TODO optimise printing of gap?
+                            for (int i = 0; i < gap; ++i) {
+                                mp_print_str(print, " ");
+                            }
+                            mp_print_str(print, d_str);
+                            line_len += gap + d_len;
+                        } else {
+                            mp_printf(print, "\n%s", d_str);
+                            line_len = d_len;
+                        }
+                    }
+                }
+            }
+            mp_print_str(print, "\n");
+
+            return (mp_uint_t)(-1); // indicate many matches
+        }
+    }
+}
+
 #endif // MICROPY_HELPER_REPL
diff --git a/py/repl.h b/py/repl.h
index db9256017603c333e937cfe6799dea9a8731e859..c34a5b86924b31724776ebd5768db2ab8ce4ac23 100644
--- a/py/repl.h
+++ b/py/repl.h
@@ -28,9 +28,11 @@
 
 #include "py/mpconfig.h"
 #include "py/misc.h"
+#include "py/mpprint.h"
 
 #if MICROPY_HELPER_REPL
 bool mp_repl_continue_with_input(const char *input);
+mp_uint_t mp_repl_autocomplete(const char *str, mp_uint_t len, const mp_print_t *print, const char **compl_str);
 #endif
 
 #endif // __MICROPY_INCLUDED_PY_REPL_H__