Git Product home page Git Product logo

tabspaces's Issues

[Sharing code] Switching tabs via frog-menu

Thanks for this package Colin! I've recently made the switch to using tabs (rather than perspectives), and I really like the simplicity of the package.

I'm sharing the code below as it might be useful to others. Hopefully this is the right place to do so.

When I have many tabs open, I like to switch between them quickly. One way to do this is via frog-menu which pops up a posframe and uses avy keys to select a candidate. More on frog-menu here: https://elpa.gnu.org/packages/frog-menu.html

In short: the code below allows to (1) pop-up a list of tabs in a posframe, and (2) switch to a tab by typing a single key (based on the first letter of the key). It takes inspiration from https://github.com/waymondo/frog-jump-buffer

(require 'frog-menu)

(defun efls/frog-tab ()
  "Pick a tab to switch to via `frog-menu'."
  (interactive)
  (let* ((frog-menu-avy-padding t)
         (frog-menu-min-col-padding 1)
         (frog-menu-display-option-alist
          '((avy-posframe . posframe-poshandler-frame-center)
            (avy-side-window display-buffer-in-side-window (side . bottom))))
         (prompt "Switch tab")
         (tabs (mapcar (lambda (tab) (alist-get 'name tab))
                       (tab-bar-tabs)))
         (frog-menu-avy-keys (efls/frog-tabs-generate-keys tabs))
         (actions '(("C-l" "List tabs"
                      (call-interactively tabspaces-switch-or-create-workspace))))
         (res (frog-menu-read prompt tabs actions)))
    (if (stringp res)
        (progn (message res)
               (tabspaces-switch-or-create-workspace res))
      (apply res))))

Generating the keys for switching perspectives is done via a separate function.

(defun efls/frog-tabs-generate-keys (tabs)
  "Generate keys for TABS (a list of tab names).
Returns a list of first chars from each tab. If two chars are
identical, make second capitalized. If more chars than two are
identical, then... nothing else."
  (let ((new-chars))
    (dolist (char (-map (lambda (s) (string (string-to-char s)))
                        tabs))
      (if (member char new-chars)
          (setq new-chars (append new-chars (list (capitalize char))))
        (setq new-chars (append new-chars (list char)))))
    (-map 'string-to-char new-chars)))

It works as follows:

  1. Create a list of tab names.
  2. Construct a list of strings with first character of each tab name.
  3. Make a new list where each repetition of a char is capitalized.
  4. Turn that list of strings into a list of characters.

There are potential issues:

  • when more than two tab names start with the same character
  • when a tab name starts with something other than an alphabetic character

Pick a license

Hello. I'm very interested in this project as the goals line up well with my own attempts to minimize large external projects and make better use of emacs built-in functionality.

I just forked the repository to add a nix flake for my own consumption (since this is not present in MELPA). When writing the flake, I realized that I had no idea what the license for this project is, which leaves me in a bit of a pickle as, while I believe you intend for this to be available for public consumption and modification, not choosing a license means you have withheld exclusive copyright.

Picking a license is your decision as the maintainer. I do not have much of an opinion but, emacs being a GNU project, the obvious choice is the GPLv3. I would suggest having a read over https://choosealicense.com and picking one. There are a number of available options, but this site does a great job of presenting the options in a concise and clear manner.

Support restoring project eshell and dired buffers in tabspaces

Feature request adding to #18.

I find that sometimes I'll only have a project-eshell buffer or dired buffer open from doing C-x p c or C x p p RET.

This means when I restore a tabspace there's not really a project open anymore ebcause those buffers aren't restored.

I then have the mild inconvenience of having to do C x p p to select the project again and reopen the dired or eshell buffer.

This request doesn't include restoring eshell output or anything, just essentially adding "this project had only an eshell buffer open so we'll do C-x p c on tab restore in that directory" logic.

Mixing tabspaces-switch-to-buffer with tab-bar-select-tab leaks buffer to another tab

I have been using this excellent package daily for around a year, but I eventually need to stop to clean up tabs because they end up with unrelated buffers. Today, I started investigating a bit more and realized it happens when I'm quickly alternating between buffers and tabs:

If I do:

  • C-x tabspaces-switch-to-buffer
    (prompt opens, asking for a buffer)
  • C-x tab-bar-select-tab
  • <enter> to load default entry.

The buffer list will be loaded from the old tab, but the switch-to-buffer will happen in the new tab.

For sure, this is user error. But can we make the interface a bit more friendly?

ideally, the minibuffer would be per-tab, but that's quite hard/impossible. A simpler solution would be exiting the minibuffer when switching tabs, but that is not very emacsy. I think we could at least detect the tab changed during tabspaces-switch-to-buffer and error out. Alternatively, we could force the switch to happen in the old tab/window where the command was initiated. Thoughts?

Customise `magit-init` call when opening a project

Recently, when opening a project in a subdirectory of a git repo (when customising project-vc-extra-root-markers to find projects in a monorepo), a new git repo is initialized in the project that then has to be deleted for magit to behave as intended.

Ideally it would be possible to customise this behaviour.

Switch between tabspaces

Hi,

I am trying to create a function which switches to the previously opened tabspace.

Suppose I have multiple tabspaces open (say, A,B,C). I am in A, and then navigate the tabspaces in the sequence A -> B -> C. I would like to make a function that switches from the current tabspace (C) to the previous one (B). Invoking this function twice in succession from C, would bring me back to C.

Any tips would be awesome. Sorry, this is not an issue, but I didn't know where else to reach out.

Cheers

Question about tabspaces-exclude-buffers

After 929e677, the default behavior of creating new tabs has changed. When creating a new tab, all the buffers in current tab would be kept in the new tab as these buffers are not in tabspaces-exclude-buffers.

tabspaces/tabspaces.el

Lines 133 to 138 in beab193

(seq-filter (lambda (buffer)
(or (member buffer window-buffers)
(member (buffer-name buffer)
tabspaces-include-buffers)
(not (member (buffer-name buffer)
tabspaces-exclude-buffers))))

Is this behavior designed to be like this? Or would it be better to use and condition with line 137-138?

error while installing from melpa

Hi,
I get the following when installing trough package-install:

In tabspaces-switch-buffer-and-tab:
tabspaces.el:322:25: Error: ‘add-to-list’ can’t use lexical var ‘tabcand’; use
    ‘push’ or ‘cl-pushnew’

although somehow installation proceeds..

Saving tabspaces isolated buffer lists with desktop.el

Using the functions desktop-save and desktop-revert (or with desktop+, desktop+-create and desktop+-load) doesn't save the complete buffer lists isolated within workspaces. It does save the workspaces created across frames, and the visible buffers each workspaces--it just fails to save the list of buffers that are not opened. Additionally, each succeeding workspace includes the buffer in the previous workspaces, even if they were not included before saving the desktop.

This is what happens (the first/leftmost buffer is the visible buffer):

Before using desktop-save and desktop-revert:

  • Workspace 1: scratch
  • Workspace 2: test.org, scratch
  • Workspace 3: test.el, random.org, dired, scratch

After using desktop-save, quitting emacs, and using desktop-revert:

  • Workspace 1: scratch
  • Workspace 2: test.org, scratch
  • Workspace 3: test.el, test.org, scratch

Is integration with desktop saving planned or possible? Since tabspaces work with builtin packages. I've tried it with burly.el too and similar problems persist.

--

Another thing, though I am not sure if I must open another issue for it: tab-bar-detach-tab and tab-bar-move-tab-to-frame has issues such that when a workspace is moved to another frame, deleting it with tabspaces-kill-buffers-close-workspace still acts as if it wasn't moved to another frame, and using the function repeatedly removes the buffers in the workspaces in the frame where the moved workspace came from.

Missing support for tab-group / integration with desktop.el

When saving / restoring tab details using tabspaces-save-session and tabspaces-restore-session, tab-group information does not seem to be handled at the moment. I started using desktop.el to save the frame setting instead, which does store the tab setup correctly including tab-group, but I won't get the correct buffer mapping with tabspaces.

What would be the approach I could take to restore all tabs and tab groups, while also keeping the buffers associated to tabs correctly? While both tabspaces-save-session and tabspaces-restore-session work pretty well on their own, is there any way to get them to work with desktop.el?

[BUG] two projects with same name are considered as one workspace

Hello,

Let's say that I have two projects at:

~/ws/proj1
~/ws/oldstuff/proj1

Opening the first project with tabspaces will create a proj1 tab. When opening the second one, it will consider that the project doesn't change and uses the same proj1 tab.

As the directories aren't the same, it should rather create another tab with something like proj1<1> or oldstuff/proj1.

Thank you for this package!

Regards

[SUGGESTION] Don't overwrite `project-switch-commands`

I didn't really know if I could figure out where in the code Tabspaces gets its current behavior since I don't know Emacs Lisp that well. But then I remembered the words of Kant that "I have no knowledge of myself as I am, but merely as I appear to myself." and tried anyway.

I noticed that if I follow these steps:

  1. M-x tabspaces-open-or-create-project-and-workspace
  2. Hover over a project from the project--list (it has to be a project that doesn't have an existing tabspace yet)
  3. Hit enter

... then Tabspaces will always run project-find-file. This behavior is hardcoded here: https://github.com/mclear-tools/tabspaces/blob/main/tabspaces.el#L392

I was wondering - would it make sense to allow users to provide their own function in place of project-find-file? That way, they could have e.g. magit-status be called instead of project-find-file.

Off the top of my head, the customization could come in the form of a new tabspaces-project-switch-commands variable in place of the hardcoded project-find-file.

On the other hand, it could potentially be more beautiful if project-switch-commands were never set to just project-find-file, and the unchanged value of project-switch-commands was used (so the project.el minibuffer prompt appears asking "what do you wanna do with this"). That way, the customization would just come from the default behavior of project.el

tabspaces-switch-or-create doesn't create empty workspace

The command tab-switch-or-create adds the current buffers to the the new workspace when creating a new workspace. I expect it to create a new workspace with no buffers attached to it or maybe it should be a separate command like tabspaces-create-empty-workspace.?

emacs-workspaces/project-switch-project-open-file vs emacs-workspaces/open-existing-project-and-workspace

This is just a question, but could you perhaps explain what each function does? I can't tell what the difference is between these two functions from their names or by invoking them...they both seem to have the same effect for me, unless I'm missing something. It might be a good idea to write descriptions in the README, or to word the documentation a bit better to avoid any confusion. Thanks!

question about your README

you wrote "Calling the minor-mode tabspaces-mode sets up newly created tabs as buffer-isolated workspaces using tab.el in the background". what is a 'buffer-isolated workspace'?

tabspaces-switch-or-create not creating new 'workspaces''

I hope I'm not mistaken here but:

Testing out tabspaces-switch-or-create-workspace doesn't seem to create any new 'workspaces'. I took a look at tabspaces.el, and it is aliased to tab-bar-switch-to-tab. Using that function doesn't create new tabs too, and instead just tries to switch tabs despite no tabs existing.

However, I do see a separate function tab-bar-new-tab / tab-bar-new-tab-to, which creates new tabs. Using the tabspaces-switch-or-create-workspace / tab-bar-switch-to-tab functions switches between the created new tabs.

Am I missing something here? Is the function supposed to work for both switch and creation of 'workspaces'? Thank you so much.

Error when opening project tab space first time

Hi,

When I try to open a project tab space (C-c TAB o) for the first time after loading emacs, it gives me a prompt to do a couple things including opening a file, but then when I open a file from that menu, I get an error and the file gets opened in the default tabspace.

Debug trace (replaced file name with FILE):

Debugger entered--Lisp error: (wrong-type-argument sequencep #<buffer FILE>)
  call-interactively(tabspaces-open-or-create-project-and-workspace nil nil)
  command-execute(tabspaces-open-or-create-project-and-workspace)

After this happens, going through the steps again works just fine (I run C-c TAB o, select my project, it automatically opens the file picker, selected file then gets moved into a new tabspace).

tabspaces with non-project buffers

Hello,

I didn't try your package yet but after skimming through the readme it seems it's pretty much for projects only.

I use Gnus and ERC, is it possible to isolate buffers related to these two modes with your package?

Thanks.

Displays *Messages* buffer

Hi, thanks for the great package! I am enjoying it, and it was integrated into Centaur Emacs.

I have a quick question: How to display the "Messages" buffer in all workspaces? I tried customizing tabspaces-include-buffers but seems not working. What was I missing?

emacs-workspaces/kill-buffers-close-workspace is looping infinitely

With a workspace containing 3 buffers, space.lisp, conditions.lisp, and *scratch*, this function loops infinitely until Emacs crashes.

I am not that great at debugging elisp code, but writing the current buffer name and the return values of kill-buffer to a file results in the following until Emacs hard-crashes:

space.lisp	t
space.lisp	t
 *Minibuf-1*	t
space.lisp	t
 *Minibuf-1*	t
conditions.lisp	t
space.lisp	t
 *Minibuf-1*	t
conditions.lisp	t
*scratch*	t

It seems that even though the buffer is successfully killed, it keeps coming back.

I am using project.el, and (project-kill-buffers t) works flawlessly, and also seems to close the tab when the last buffer is killed, so I'm using that for now, as I couldn't debug the problem any further.

Rename created tabspace

Once you have created a tabspace, is there a way to rename it? Specially, I would want to rename the first tabspace created in a session.

It tends to stay with the name of the first buffer within it, while on creating the rest of tabspaces I can pick a name that is meaningful to the tasks I intend to solve in it.

when switching buffer and tab to an existing buffer, it gets added to the current tabspace

Hi, great package!

If I'm in a tabspace and I open a buffer that's already opened in another tabspace, and I've specified I want to switch to its tab (using tabspaces-switch-buffer-and-tab), I expect the buffer not to be added to the current tab as well - or to be prompted if I want to deliberately add it.
With time, this totally defeats the point of segregating buffers between tabs, in my opinion.

Thanks!

Switch buffer and tab from ibuffer

Not sure if this is already possible somehow, but it would be awesome if we could use ibuffer, and simply jump to the correct tab by executing a command on a buffer from the ibuffer list.
You can do this manually with tabspaces-switch-buffer-and-tab, but this does not seem to be integrated into ibuffer (yet).

C-c TAB b to switch buffers always lists the current buffer at the top of the list

This is different from how Emacs does buffer switching by default, which is to not list the current buffer in the list of candidates at all. This screenshot shows the behavior I'm describing. The default local buffer to switch to should not be the current buffer, but instead (in this case) it should be cus-edit.el.gz instead.

image

Integration with projectile

Hey there!

Thanks a lot for creating tabspaces. I wanted to ask if there is any interest from your side to support projectile?

Projectile is pretty big part of my personal Emacs config and I was looking into tabspaces source which changes would need to be made in order to use projectile instead of project.el.

For example, there could be a variable like

(defcustom tabspaces-projectile-integration nil
  "Whether to use projectile instead of project.el.
This variable must be set before tabspaces is loaded."
  :type 'boolean
  :group 'tabspaces)

which then controls how projects are switched, etc.

If yes, I can prepare a PR as soon as I am done.

Unusable `tabspaces-open-or-create-project-and-workspace`

Hello and thank you for your responsiveness on issue #38

The commit e937743 broke the workflow! Now, calling tabspaces-open-or-create-project-and-workspace on existing projects asks each time for repo initialization as Git, Hg, ...

Also, the tabspaces--generate-unique-tab-name function causes opening the same project twice to create two tabs with names "project" and "project<1>".

I think the commit should be revoked until this gets fixed, in my config, I'm explicitly using the commit before this one.

I will try to work on this the next weekend and open a PR.

Thank you again for this awesome package!

Is it possible to create workspaces based on modes?

Hi, Thanks for this great package, this is so useful!

I wonder if there is way to create workspaces based on modes? For example, besides from having projects in different workspaces, I would also like to keep all help buffers on a different workspace, and all magit buffers on another workspace.

tabspaces-open-existing-project-and-workspace includes current buffer in new workspace

tabspaces-create-workspace will include the current buffer in the newly created workspace. This means that tabspaces-open-existing-project-and-workspace includes it as well. In my custom command, I have to remove it with tabspaces-remove-selected-buffer:

(defun aj/tabspaces-switch-project (project-to-switch)
    "Switch to project tab and find project file.
Only if the switched to buffer is not of that project."
    (interactive (list (project-prompt-project-dir)))

    (let* ((tab-names (mapcar (lambda (tab) (alist-get 'name tab)) (funcall tab-bar-tabs-function)))
           (project-name (aj/project-name project-to-switch))
           (tab-name project-name)
           new-tab)
      (if (member tab-name tab-names)
          (tab-bar-select-tab-by-name tab-name)
        (setq new-tab t)
        (tabspaces-create-workspace)
        (tab-bar-rename-tab tab-name))
      (unless (string= project-name (aj/project-name))
        (let ((default-directory project-to-switch)
              (previous-buffer (current-buffer)))
          (project-find-file)
          (when new-tab
            (tabspaces-remove-selected-buffer previous-buffer))))))

[BUG] two projects with same name are considered as one workspace

Hello @mclearc

The issue I've reported previously in #38 is raising again, I think after this commit a260cd8.

I think the idea behind the above commit is good (no duplicate tabs for the same project unless intended). However, it fails to distinguishes (given it uses only tab names) between two projects with the same name (with different paths), and two instances of the same project (with the same path).

tabspaces/tabspaces.el

Lines 402 to 409 in beab193

(defun tabspaces--generate-unique-tab-name (base-name existing-names)
"Generate a unique tab name based on BASE-NAME and the list of EXISTING-NAMES."
(let ((new-name base-name)
(count 1))
(while (member new-name existing-names)
(setq new-name (format "%s<%d>" base-name count))
(setq count (1+ count)))
new-name))

I think it would be more logical to store (somehow) the absolute path for each project of each tab. I don't know if there is an obvious way to add tab-local variables (like buffer local with defvar-local and setq-local), but I think this is the right way to do it (instead of relaying only on tab names).

help understanding buffer-isolated tabspaces

I'm just trying to figure out if this package will work for me and wanted to run a couple of scenarios by you.

  • I'm working in a project in one tab. I realize I need to do something in another project. I select the relevant buffer or file with tabsapces-switch-to-buffer (can I even do that?). Does the buffer open for me in the appropriate tab for its own project?

  • I have one tab that I use for mail, using mu4e. mu4e creates a bunch of its own buffers, and I also have a dashboard buffer on the side (using mu4e-dashboard). When I create the workspace for this tab, can I specify that all of those buffers should be included in the workspace filtered buffer list? That is, can I specify my own pseudo-project that holds things like mail, rss feeds, etc?

Maybe the answers to these questions are obvious, but I didn't immediately see how to do them when I looked at the code.

Thank you!

Add tabspaces-session-file to no-littering

I have submitted a PR to the no-littering emacs package to include configuration of tabspaces' tabspaces-session-file variable.

No-littering package is an attempt to keep user-emacs-directory and source directories tidy by assigning standard locations for data and config files. The files and directories are also expected to follow certain naming conventions.

In addition to informing you, I hope you can confirm that this is the only data file saved by tabspaces.

Thanks!

PS: A discussion thread would have been more appropriate for this. Please feel free to close this issue.

[SUGGESTION] a nicer tabspaces-ivy-switch-buffer

(defun tabspaces-ivy-switch-buffer ()
  "Switch to another buffer in the current tabspace."
  (interactive)
  (ivy-read "Switch to buffer: " #'internal-complete-buffer
            :predicate (when (tabspaces--current-tab-name)
                         (let ((local-buffers (tabspaces--buffer-list)))
                           (lambda (name-and-buffer)
                             (member (cdr name-and-buffer) local-buffers))))
            :keymap ivy-switch-buffer-map
            :preselect (buffer-name (other-buffer (current-buffer)))
            :action #'ivy--switch-buffer-action
            :matcher #'ivy--switch-buffer-matcher
            :caller 'ivy-switch-buffer))

This is a straight clone of ivy-switch-buffer, with added predicate that only allows buffers from the current tabspace. The :caller is left as 'ivy-switch-buffer to let Ivy do its thing of appending dead buffers from history to the list; it would be neat to limit those to the current tabspace's history also but I have no idea how.

But of course it uses Ivy implementation details, so there's that.

[SUGGESTION] Enable tabspace duplication only when `tabspaces-open-or-create-project-and-workspace` is called with C-u prefix

This is related to #42

In the issue, the reporter says:

Also, the tabspaces--generate-unique-tab-name function causes opening the same project twice to create two tabs with names "project" and "project<1>".

And Colin replied:

Note though that the duplicate tab function is a feature not a bug. Some folks might want two different workspaces for the same project.


While the change supports a valid usecase, I miss how tabspaces-open-or-create-project-and-workspace could've been used mindlessly, without having to think if the tabspace is already open. Sure, you can use C-C TAB s now instead, but as I said, the "mindlessness" was a nice bonus imo. I frequently have so many tabspaces open that it's not immediately clear if the tabspace is already there or not.

Here are some ideas:

  1. I was wondering if it'd be possible to have two modes of operation for tabspaces-open-or-create-project-and-workspace. If a prefix C-u (https://www.emacswiki.org/emacs/PrefixArgument) is used, use the new "duplicate tabspace if already existing". If no prefix, use old "switch to tabspace if already exists". So: C-u C-c TAB o -> duplicate tabspace always; C-c TAB o -> switch to tabspace if it exists

  2. (nitpick) If you prefer the current behavior to stay, the docstring at https://github.com/mclear-tools/tabspaces/blob/main/tabspaces.el#L399 should be changed to say that the function will duplicate the tabspace if it exists, not switch to it. I can do that if you want

This may be a case of https://xkcd.com/1172/, so if I'm being a baby about it and should now just use C-c TAB s instead of C-C TAB o I'll oblige :P

advise tab-bar-new-tab?

Would it make sense to advise tab-bar-new-tab, rather than providing a separate tabspaces-create-workspace function? If that were done, then things like tab-bar-switch-to-tab could be used out of the box, rather than tabspaces-switch-to-or-create-workspace.

`tabspaces-session-auto-restore t` doesn't autorestore when switching to project

So whenever I want to switch to a project using tabspaces-open-or-create-project-and-workspace I would like it to auto restore the session. But currently that is not happening.

  • tabspaces-mode seems to be enabled on startup
  • Manually invoking tabspaces-restore-session loads the buffers I expect
  • Currently whenever I open-or-create-project-and-workspace I only get a find file pop up to find files in the project.

config

My current config for tabspaces using elpaca:

(use-package tabspaces
  :ensure t
  :hook (emacs-startup . tabspaces-mode)
  :commands (tabspaces-switch-or-create-workspace
             tabspaces-open-or-create-project-and-workspace)
  :custom
  (tabspaces-keymap-prefix "C-c p")
  (tabspaces-use-filtered-buffers-as-default t)
  (tabspaces-include-buffers '("*scratch*"))
  (tabspaces-default-tab "Home")
  ;; sessions
  (tabspaces-session t)
  (tabspaces-session-auto-restore t))

(provide 'init-project)

image

Searching files in a workspace

Hi! Thank you for the great plugin. It's exactly the simplicity I was looking for.
I'm only missing one thing, and that is a command/function that searches through the current project(starting from the defined root and N folders deep), while respecting .gitignore.

I don't think this needs to be a function for tabspaces, but I'm new in Emacs-land and I was wondering what would be the best way.

emacs-workspaces--project-name potential problems

Forgive me, as I'm not an Emacs Lisp coder (Common Lisp actually), but I find some things possibly wrong with the cond form here

  • (vc-root-dir) could be lexically bound as to not call it twice.
  • the vc-call-backend branch seems to not have a value and is returning nil?
  • The catch-all t branch is returning the same string as the previous branch, making the previous branch un-necessary?

Submit to MELPA

Once #4 is resolved, I believe that this project would see a lot more usage if it were submitted to melpa (rather than requiring straight or manual cloning). I have not yet looked deeply into how to achieve this, but I do know that there will need to be a recipe created and a maintainer for that recipe selected. MELPA's repo suggests that the emacs package author take over the MELPA recipe author role for their particular packages.

If there's anything I can do to help in this effort, please don't hesitate to ask. Thank you.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.